{
  "openapi": "3.0.0",
  "info": {
    "title": "Plek Booking API",
    "description": "The Plek API: organisations, their activities, sessions, series, bookings and customers.\n\nGuides, conventions (money in cents, timezones, pagination, errors) and a test key for trying it out:\nhttps://development.plekbooking.com\n\n## Three APIs on one host\n\n- **Staff API** (`/api/org/{organisation_id}/...`): what staff can see and do in the admin app. Authenticate\n  with an organisation API key (`X-Api-Key: plek_live_...` or `Authorization: Bearer plek_live_...`), which\n  carries the permissions of its role, or with a staff access token.\n- **Public booking API** (`/api/public/{org_slug}/...`): what the booking site and widgets show and book,\n  without authentication.\n- **Customer API** (`/api/customer/{org_slug}/...`): a signed-in customer's own account, with a customer token.\n\n## Rate limits\n\nRequests draw from token-bucket budgets per API key and per organisation, with separate budgets for\nreads, writes and expensive reads (exports, reports, analytics, free-slot search). Responses carry the\nIETF `RateLimit-Policy` and `RateLimit` headers (draft-ietf-httpapi-ratelimit-headers); beyond a limit\nthe API answers `429 Too Many Requests` with `Retry-After` and `{\"code\": \"rate_limited\", \"retryAfter\": 20}`.\nThe budgets: https://development.plekbooking.com/guides/rate-limits.\n\n## Webhooks\n\nOrganisations subscribe URLs to events. Every delivery is a JSON POST signed with HMAC-SHA256 in the\n`Plek-Signature: t={timestamp},v1={hex}` header, over `{timestamp}.{body}` with the webhook's secret.\nSee https://development.plekbooking.com/guides/webhooks.",
    "contact": {
      "name": "Plek",
      "url": "https://development.plekbooking.com"
    },
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://api.plekbooking.com"
    }
  ],
  "paths": {
    "/api/org/{organisation_id}/checkouts/abandoned": {
      "get": {
        "tags": [
          "Bookings"
        ],
        "summary": "Unfinished checkouts",
        "description": "Checkouts whose bookings all expired unpaid in the last 30 days, for sessions and series still to come, newest first (#747). The assigned scope only lists sessions the member coaches. Customer name and email only with customer:read. Needs booking:read.",
        "operationId": "listAbandonedCheckouts",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "status",
            "in": "query",
            "description": "open: not booked again since; booked: the customer holds it again",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "booked"
              ]
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page, from 1",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Items per page",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Checkouts",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "checkouts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffAbandonedCheckout"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/StaffPageMetadata"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/checkouts/abandoned/{checkout_id}/pay-link": {
      "post": {
        "tags": [
          "Bookings"
        ],
        "summary": "Send a pay link",
        "description": "Places a fresh checkout for the same participants or tickets at today's prices (no discount codes or gift cards), held for the waiting list hold time (at most until the start), and emails checkout_pay_link with a button to pay. The expired holds are never revived. Errors (code): 404 not_found, 409 not_abandoned, already_booked, past, no_customer, no_seats, not_bookable. Needs booking:write:all.",
        "operationId": "sendAbandonedCheckoutPayLink",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "checkout_id",
            "in": "path",
            "description": "Checkout id (or the booking id of a booking without one)",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Placed and emailed",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "checkoutId": {
                      "type": "string"
                    },
                    "bookingIds": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "format": "uuid"
                      }
                    },
                    "heldUntil": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    },
                    "amountDue": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/StaffAmount"
                        }
                      ],
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Checkout not found (not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "409": {
            "description": "Cannot be booked again",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/customers/{id}/credit": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Account credit of a customer",
        "description": "Credit issued to the customer (#752) and what they can spend now per currency. Needs payment:read.",
        "operationId": "listCustomerAccountCredit",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Customer id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Credit",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "balances": {
                      "description": "Spendable now, per currency",
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffAmount"
                      }
                    },
                    "credits": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffAccountCredit"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Customer not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Issue account credit",
        "description": "A gift card only this customer can spend (#752); the customer gets an email. Audit-logged (customer.credit_issued). Needs payment:write.",
        "operationId": "issueCustomerAccountCredit",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Customer id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "amount",
                  "currency",
                  "reason"
                ],
                "properties": {
                  "amount": {
                    "description": "Cents",
                    "type": "integer"
                  },
                  "currency": {
                    "type": "string",
                    "example": "EUR"
                  },
                  "reason": {
                    "type": "string"
                  },
                  "expiresOn": {
                    "description": "Local date; null or left out: no expiry",
                    "type": "string",
                    "format": "date",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Issued",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "credit": {
                      "$ref": "#/components/schemas/StaffAccountCredit"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Customer not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "Invalid amount, currency, reason or date (code invalid)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/credit": {
      "get": {
        "tags": [
          "Customer - Plans and credit"
        ],
        "summary": "My account credit",
        "description": "Account credit staff issued to the customer (#752): balances per currency and each credit (never its code: it is spent at checkout or plan purchase with `useAccountCredit: true`, expiring first first). Verified customers only.",
        "operationId": "customerGetAccountCredit",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Credit",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "balances": {
                      "description": "Usable balance per currency",
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerAmount"
                      }
                    },
                    "credits": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerCredit"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/activities": {
      "get": {
        "tags": [
          "Activities"
        ],
        "summary": "List activities",
        "description": "All activities of the organisation, paginated. Needs activity:read.",
        "operationId": "listActivities",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page, from 1",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Items per page",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Activities",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "activities": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffActivity"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/StaffPageMetadata"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Activities"
        ],
        "summary": "Create an activity",
        "description": "Starts unpublished, booked per participant. Translations are checked before anything is created. Needs activity:write.",
        "operationId": "createActivity",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffActivityInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffActivityResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input (e.g. age rule, translations, currency)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/activities/{id}": {
      "get": {
        "tags": [
          "Activities"
        ],
        "summary": "One activity",
        "description": "Needs activity:read.",
        "operationId": "getActivity",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The activity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffActivityResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid activity id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Activities"
        ],
        "summary": "Change an activity",
        "description": "name and defaultPrice are required; audience, age rule, billing details, product code and translations change only when sent. Needs activity:write.",
        "operationId": "updateActivity",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffActivityInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffActivityResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Activities"
        ],
        "summary": "Delete an activity",
        "description": "Needs activity:write.",
        "operationId": "deleteActivity",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/activities/{id}/images": {
      "post": {
        "tags": [
          "Activities"
        ],
        "summary": "Upload pictures",
        "description": "Multipart upload of one or more files in the field images (or images[]). Needs activity:write.",
        "operationId": "uploadActivityImages",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "properties": {
                  "images[]": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The activity with its pictures",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffActivityResponse"
                }
              }
            }
          },
          "400": {
            "description": "No images, upload error or invalid file",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/activities/{id}/images/{image_id}": {
      "delete": {
        "tags": [
          "Activities"
        ],
        "summary": "Remove a picture",
        "description": "Needs activity:write.",
        "operationId": "deleteActivityImage",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "image_id",
            "in": "path",
            "description": "Image id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The activity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffActivityResponse"
                }
              }
            }
          },
          "400": {
            "description": "Unknown activity or image",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/activities/{id}/images/reorder": {
      "patch": {
        "tags": [
          "Activities"
        ],
        "summary": "Reorder pictures",
        "description": "Needs activity:write.",
        "operationId": "reorderActivityImages",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "orderedImageIds": {
                    "description": "All image ids in the new order",
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The activity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffActivityResponse"
                }
              }
            }
          },
          "400": {
            "description": "orderedImageIds missing or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/activities/{id}/primary-image": {
      "patch": {
        "tags": [
          "Activities"
        ],
        "summary": "Choose the main picture",
        "description": "Needs activity:write.",
        "operationId": "setActivityPrimaryImage",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "imageId": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The activity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffActivityResponse"
                }
              }
            }
          },
          "400": {
            "description": "imageId missing or unknown",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/activities/{id}/publish": {
      "post": {
        "tags": [
          "Activities"
        ],
        "summary": "Publish an activity",
        "description": "Customers can book it online once published. Needs activity:write.",
        "operationId": "publishActivity",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The activity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffActivityResponse"
                }
              }
            }
          },
          "400": {
            "description": "Unknown activity or cannot be published",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/activities/{id}/unpublish": {
      "post": {
        "tags": [
          "Activities"
        ],
        "summary": "Unpublish an activity",
        "description": "Customers can no longer book it online. Needs activity:write.",
        "operationId": "unpublishActivity",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The activity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffActivityResponse"
                }
              }
            }
          },
          "400": {
            "description": "Unknown activity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/analytics/overview": {
      "get": {
        "tags": [
          "Reports and analytics"
        ],
        "summary": "Dashboard overview",
        "description": "Confirmed bookings, money received (#711, #767), new customers and occupancy in the period, each with the previous period of the same length. Headline money is in the organisation's currency, `byCurrency` has every currency (never added up). Needs analytics:read.",
        "operationId": "getAnalyticsOverview",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "period",
            "in": "query",
            "description": "Up to today; custom takes from and to",
            "required": false,
            "schema": {
              "type": "string",
              "default": "30d",
              "enum": [
                "7d",
                "30d",
                "90d",
                "12m",
                "custom"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "With period=custom",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "With period=custom",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Overview",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "overview": {
                      "properties": {
                        "bookings": {
                          "properties": {
                            "count": {
                              "type": "integer"
                            },
                            "previousCount": {
                              "type": "integer"
                            }
                          },
                          "type": "object"
                        },
                        "revenue": {
                          "type": "object",
                          "allOf": [
                            {
                              "$ref": "#/components/schemas/AnalyticsRevenue"
                            },
                            {
                              "properties": {
                                "byCurrency": {
                                  "type": "array",
                                  "items": {
                                    "$ref": "#/components/schemas/AnalyticsRevenue"
                                  }
                                }
                              },
                              "type": "object"
                            }
                          ]
                        },
                        "newCustomers": {
                          "properties": {
                            "count": {
                              "type": "integer"
                            },
                            "previousCount": {
                              "type": "integer"
                            }
                          },
                          "type": "object"
                        },
                        "occupancy": {
                          "properties": {
                            "rate": {
                              "type": "number"
                            },
                            "previousRate": {
                              "type": "number"
                            }
                          },
                          "type": "object"
                        }
                      },
                      "type": "object"
                    },
                    "period": {
                      "$ref": "#/components/schemas/AnalyticsPeriod"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/analytics/bookings": {
      "get": {
        "tags": [
          "Reports and analytics"
        ],
        "summary": "Bookings per day",
        "description": "Bookings made per day in the period: confirmed, cancelled and total. Needs analytics:read.",
        "operationId": "getAnalyticsBookings",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "period",
            "in": "query",
            "description": "Up to today; custom takes from and to",
            "required": false,
            "schema": {
              "type": "string",
              "default": "30d",
              "enum": [
                "7d",
                "30d",
                "90d",
                "12m",
                "custom"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "With period=custom",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "With period=custom",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Per day",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "bookings": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "date": {
                            "type": "string",
                            "format": "date"
                          },
                          "confirmed": {
                            "type": "integer"
                          },
                          "cancelled": {
                            "type": "integer"
                          },
                          "total": {
                            "type": "integer"
                          }
                        },
                        "type": "object"
                      }
                    },
                    "period": {
                      "$ref": "#/components/schemas/AnalyticsPeriod"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/analytics/revenue": {
      "get": {
        "tags": [
          "Reports and analytics"
        ],
        "summary": "Money per day",
        "description": "Per day and currency (minor units): received, outstanding, paid with gift card, refunded, booked, and what was received per source. Needs analytics:read.",
        "operationId": "getAnalyticsRevenue",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "period",
            "in": "query",
            "description": "Up to today; custom takes from and to",
            "required": false,
            "schema": {
              "type": "string",
              "default": "30d",
              "enum": [
                "7d",
                "30d",
                "90d",
                "12m",
                "custom"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "With period=custom",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "With period=custom",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Per day and currency",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "revenue": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "date": {
                            "type": "string",
                            "format": "date"
                          },
                          "currency": {
                            "type": "string"
                          },
                          "received": {
                            "type": "integer"
                          },
                          "outstanding": {
                            "type": "integer"
                          },
                          "paidWithGiftCard": {
                            "type": "integer"
                          },
                          "refunded": {
                            "type": "integer"
                          },
                          "booked": {
                            "type": "integer"
                          },
                          "bookings": {
                            "description": "Received from bookings",
                            "type": "integer"
                          },
                          "plans": {
                            "type": "integer"
                          },
                          "giftCardSales": {
                            "type": "integer"
                          },
                          "fees": {
                            "type": "integer"
                          },
                          "hires": {
                            "type": "integer"
                          }
                        },
                        "type": "object"
                      }
                    },
                    "period": {
                      "$ref": "#/components/schemas/AnalyticsPeriod"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/analytics/activities": {
      "get": {
        "tags": [
          "Reports and analytics"
        ],
        "summary": "Popular activities",
        "description": "Activities by confirmed bookings in the period, with the money received per currency (bookings only). Needs analytics:read.",
        "operationId": "getAnalyticsActivities",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "period",
            "in": "query",
            "description": "Up to today; custom takes from and to",
            "required": false,
            "schema": {
              "type": "string",
              "default": "30d",
              "enum": [
                "7d",
                "30d",
                "90d",
                "12m",
                "custom"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "With period=custom",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "With period=custom",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 10,
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Activities",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "activities": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "bookings": {
                            "type": "integer"
                          },
                          "revenue": {
                            "description": "Received, in `currency` (the first of `revenues`)",
                            "type": "integer"
                          },
                          "currency": {
                            "type": "string"
                          },
                          "revenues": {
                            "type": "array",
                            "items": {
                              "$ref": "#/components/schemas/AnalyticsRevenueEntry"
                            }
                          }
                        },
                        "type": "object"
                      }
                    },
                    "period": {
                      "$ref": "#/components/schemas/AnalyticsPeriod"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/analytics/customers": {
      "get": {
        "tags": [
          "Reports and analytics"
        ],
        "summary": "Customer insights",
        "description": "New customers, the share of returning customers and the top customers by what they paid (everything, not only bookings). Needs analytics:read.",
        "operationId": "getAnalyticsCustomers",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "period",
            "in": "query",
            "description": "Up to today; custom takes from and to",
            "required": false,
            "schema": {
              "type": "string",
              "default": "30d",
              "enum": [
                "7d",
                "30d",
                "90d",
                "12m",
                "custom"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "With period=custom",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "With period=custom",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 10,
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Insights",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "customers": {
                      "properties": {
                        "newCustomers": {
                          "type": "integer"
                        },
                        "returningRate": {
                          "type": "number"
                        },
                        "topCustomers": {
                          "type": "array",
                          "items": {
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "name": {
                                "type": "string"
                              },
                              "email": {
                                "type": "string"
                              },
                              "bookings": {
                                "type": "integer"
                              },
                              "revenue": {
                                "type": "integer"
                              },
                              "currency": {
                                "type": "string"
                              },
                              "revenues": {
                                "type": "array",
                                "items": {
                                  "$ref": "#/components/schemas/AnalyticsRevenueEntry"
                                }
                              }
                            },
                            "type": "object"
                          }
                        }
                      },
                      "type": "object"
                    },
                    "period": {
                      "$ref": "#/components/schemas/AnalyticsPeriod"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/api-usage": {
      "get": {
        "tags": [
          "Integrations"
        ],
        "summary": "API rate limits and usage",
        "description": "The organisation's request budgets per minute (per API key, per temporary key, per MCP connection, for the whole organisation, per staff member and per visitor), where each active API key's and connected app's budgets stand right now, and their requests and refused (429) requests over the last 24 hours. Staff only, needs integration:read.",
        "operationId": "getApiUsage",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Usage",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "windowSeconds": {
                      "type": "integer",
                      "example": 60
                    },
                    "limits": {
                      "description": "Requests per window per scope and class (read, write, expensive; mcp: any); 0 = no limit",
                      "type": "object",
                      "example": {
                        "key": {
                          "read": 600,
                          "write": 120,
                          "expensive": 20
                        },
                        "organisation": {
                          "read": 1200,
                          "write": 300,
                          "expensive": 40
                        }
                      }
                    },
                    "organisation": {
                      "properties": {
                        "current": {
                          "description": "Per class: quota, used, remaining",
                          "type": "object"
                        },
                        "last24h": {
                          "properties": {
                            "requests": {
                              "type": "integer"
                            },
                            "limited": {
                              "type": "integer"
                            }
                          },
                          "type": "object"
                        }
                      },
                      "type": "object"
                    },
                    "credentials": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "type": {
                            "type": "string",
                            "enum": [
                              "api_key",
                              "oauth"
                            ]
                          },
                          "name": {
                            "type": "string"
                          },
                          "temporary": {
                            "type": "boolean"
                          },
                          "current": {
                            "description": "Per class (read, write, expensive, mcp): quota, used, remaining",
                            "type": "object"
                          },
                          "last24h": {
                            "properties": {
                              "requests": {
                                "type": "integer"
                              },
                              "limited": {
                                "type": "integer"
                              }
                            },
                            "type": "object"
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/appointments/affected": {
      "get": {
        "tags": [
          "Appointments"
        ],
        "summary": "Appointments that need another staff member",
        "description": "Booked appointments to come whose staff member left, no longer provides the service or is off then (#927), with the colleagues who could take over. The assigned scope only lists the caller's own. Needs booking:read.",
        "operationId": "listAffectedAppointments",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "userId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "activityId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Appointments",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "appointments": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AffectedAppointment"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/appointments/{id}/reassign": {
      "post": {
        "tags": [
          "Appointments"
        ],
        "summary": "Hand an appointment over",
        "description": "To a staff member who provides the service there and is free then (#927), under the slot locks. The customers get the appointment_reassigned email (#930) unless notify is false. Audit-logged (appointment.reassigned). Needs booking:write:all.",
        "operationId": "reassignAppointment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "staffId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "notify": {
                    "type": "boolean",
                    "default": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Handed over",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AffectedAppointment"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "slot_taken: not free then",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "not_provider or validation_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/appointments/{id}/move": {
      "get": {
        "tags": [
          "Appointments"
        ],
        "summary": "An appointment to move",
        "description": "The booked appointment (by booking id) as staff move it (#933): when and with whom it is, the people it can go to (only the caller for the assigned scope), the customer and how they would hear about it. Needs booking:write or appointment:write; the assigned scope only reaches the caller's own appointments.",
        "operationId": "getAppointmentMove",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "The booking, or the appointment's session when one booking holds it",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The appointment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppointmentMoveContext"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Appointments"
        ],
        "summary": "Move an appointment",
        "description": "To another moment and/or staff member of its service there (#933), under the slot locks; never over busy time, outside the schedule or opening hours allowed (outsideSchedule in the answer). The booking keeps its payments, discounts and pass use. The customer gets the appointment_moved email (and SMS/push when they get those) unless notify is false; note goes into the email. Audit-logged (appointment.moved). Needs booking:write or appointment:write; the assigned scope only moves its own appointments and keeps them (another staff member needs the all scope).",
        "operationId": "moveAppointment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppointmentMoveRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Moved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MovedAppointment"
                }
              }
            }
          },
          "403": {
            "description": "staff_change_not_allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "slot_taken, not_movable or already_booked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "not_provider, same_moment or validation_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/appointments/{id}/move/slots": {
      "get": {
        "tags": [
          "Appointments"
        ],
        "summary": "Moments an appointment can move to",
        "description": "Per local date, the moments the appointment can move to with a staff member (default: its own): busy time is never offered, the appointment's own time is free to move within. outsideSchedule: outside the staff member's availability or the opening hours (allowed, with a warning); current: where it is now. Needs booking:write or appointment:write.",
        "operationId": "listAppointmentMoveSlots",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 7,
              "maximum": 14,
              "minimum": 1
            }
          },
          {
            "name": "staffId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Moments",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppointmentMoveSlots"
                }
              }
            }
          },
          "403": {
            "description": "staff_change_not_allowed: the assigned scope only sees its own times",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "invalid_range or not_provider",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/appointments/{id}/move/preview": {
      "post": {
        "tags": [
          "Appointments"
        ],
        "summary": "What the customer would be sent",
        "description": "The email (subject, html, text), SMS and push notification the customer would get for this move, in their language; sms and push are null when they don't reach them. Nothing is changed or sent. Needs booking:write or appointment:write.",
        "operationId": "previewAppointmentMove",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppointmentMoveRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Preview",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppointmentMovePreview"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "validation_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/attendance": {
      "get": {
        "tags": [
          "Series"
        ],
        "summary": "Attendance register of a series",
        "description": "Participants × sessions of a series with present / absent / excused / unmarked and rates; parties counted in people (#343, #798). Needs attendance:read; with the assigned scope only series the user coaches.",
        "operationId": "getSeriesAttendance",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The register",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SeriesAttendanceRegister"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/participants/{id}/attendance": {
      "get": {
        "tags": [
          "Participants"
        ],
        "summary": "Attendance of a participant",
        "description": "Totals and the latest sessions of the participant's confirmed bookings that already started (#343). Needs `attendance:read` (assigned scope: participants and sessions the member coaches; members limited to locations: sessions there).",
        "operationId": "getParticipantAttendance",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Attendance",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffParticipantAttendance"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/messages": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "Messages sent to a session's attendees",
        "description": "The messages sent, with delivery counts, and how many people a new message would reach (counts only, no names or addresses). With the assigned scope only sessions the caller coaches. Needs message:write.",
        "operationId": "listSessionAttendeeMessages",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Messages and recipient counts",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "messages": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SessionAttendeeMessage"
                      }
                    },
                    "recipients": {
                      "properties": {
                        "attendees": {
                          "type": "integer"
                        },
                        "withWaitlist": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Email a session's attendees",
        "description": "Emails everyone booked (optionally also the waiting list). Needs message:write; with the assigned scope only sessions the caller coaches.",
        "operationId": "sendSessionAttendeeMessage",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "subject",
                  "body"
                ],
                "properties": {
                  "subject": {
                    "type": "string"
                  },
                  "body": {
                    "type": "string"
                  },
                  "includeWaitlist": {
                    "type": "boolean",
                    "default": false
                  },
                  "locale": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Sent",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "$ref": "#/components/schemas/SessionAttendeeMessage"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "subject and body are required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "code no_attendees: nobody to send it to",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/messages": {
      "get": {
        "tags": [
          "Series"
        ],
        "summary": "Messages sent to the attendees of a series",
        "description": "Sent messages with delivery counts, and how many would receive a new one (counts only, no names or addresses) (#394). Needs message:write; with the assigned scope only series the user coaches.",
        "operationId": "listSeriesMessages",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Messages and recipient counts",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "messages": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SeriesAttendeeMessage"
                      }
                    },
                    "recipients": {
                      "properties": {
                        "attendees": {
                          "type": "integer"
                        },
                        "withWaitlist": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Series"
        ],
        "summary": "Email the attendees of a series",
        "description": "Queues an email to everyone booked on the series, optionally the waiting list too (#394). Needs message:write; with the assigned scope only series the user coaches.",
        "operationId": "sendSeriesMessage",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "subject",
                  "body"
                ],
                "properties": {
                  "subject": {
                    "type": "string"
                  },
                  "body": {
                    "type": "string"
                  },
                  "includeWaitlist": {
                    "type": "boolean",
                    "default": false
                  },
                  "locale": {
                    "description": "Language of the email; default the current one",
                    "type": "string",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Sent",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "$ref": "#/components/schemas/SeriesAttendeeMessage"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "subject and body are required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "no_attendees: nobody to send it to",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookings/series/reserve": {
      "post": {
        "tags": [
          "Bookings"
        ],
        "summary": "Book a participant into a series",
        "description": "Creates a pending booking of the whole series for a participant, as staff (late sign-up and booking window rules don't apply). Confirm it with POST /bookings/{id}/confirm or take a payment. Needs `booking:write` (assigned scope: series the member coaches; members limited to locations: series there). 422 codes: the age rule (`age_too_young`, `age_too_old`, `age_unknown`; send ignoreAgeRules to confirm) and headcount refusals (`headcount_series_not_supported`); 400 for other refusals (e.g. `already_booked`, `not_available`, currency codes).",
        "operationId": "reserveStaffSeriesBooking",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffBookingReserveInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Reserved",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "booking": {
                      "$ref": "#/components/schemas/StaffBooking"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "participantId and seriesId are required, or the booking was refused",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Age requirement or headcount activity; `code` says which",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookings/session/reserve": {
      "post": {
        "tags": [
          "Bookings"
        ],
        "summary": "Book a participant into a session",
        "description": "Creates a pending booking of one session for a participant, as staff. Confirm it with POST /bookings/{id}/confirm or take a payment. Needs `booking:write` (assigned scope: sessions the member coaches; members limited to locations: sessions there). 422 codes: trial refusals (trial: true), the age rule (`age_too_young`, `age_too_old`, `age_unknown`; send ignoreAgeRules to confirm) and headcount activities (`headcount_only`: book a party instead); 400 for other refusals (e.g. `already_booked`, `not_available`, currency codes).",
        "operationId": "reserveStaffSessionBooking",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffBookingReserveInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Reserved",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "booking": {
                      "$ref": "#/components/schemas/StaffBooking"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "participantId and sessionId are required, or the booking was refused",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Trial, age requirement or headcount activity; `code` says which",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookings/{id}/confirm": {
      "post": {
        "tags": [
          "Bookings"
        ],
        "summary": "Confirm a pending booking",
        "description": "Needs `booking:write` (assigned scope: bookings of sessions the member coaches).",
        "operationId": "confirmStaffBooking",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Confirmed",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "booking": {
                      "$ref": "#/components/schemas/StaffBooking"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Not a pending booking",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookings/{id}/initiate-payment": {
      "post": {
        "tags": [
          "Bookings"
        ],
        "summary": "Start an online payment for a booking",
        "description": "Creates a payment at the organisation's gateway and returns the checkout URL to send the payer to. Needs `booking:write` (assigned scope: bookings of sessions the member coaches).",
        "operationId": "initiateStaffBookingPayment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "returnUrl"
                ],
                "properties": {
                  "returnUrl": {
                    "description": "Where the gateway sends the payer back; checked against the allowed origins",
                    "type": "string",
                    "format": "uri"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Payment started",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "payment": {
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "status": {
                          "type": "string"
                        },
                        "amount": {
                          "$ref": "#/components/schemas/StaffBookingStringAmount"
                        },
                        "checkoutUrl": {
                          "type": "string",
                          "nullable": true
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "returnUrl is required, or the payment was refused (`code` for currency errors)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookings/{id}": {
      "get": {
        "tags": [
          "Bookings"
        ],
        "summary": "One booking",
        "description": "With display names, discount, gift cards, balance, the cancellation policy's outcome and the booking answers. Needs `booking:read` (assigned scope: bookings of sessions the member coaches; members limited to locations: bookings there).",
        "operationId": "getStaffBooking",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The booking",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "booking": {
                      "$ref": "#/components/schemas/StaffBookingDetail"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid booking ID",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Bookings"
        ],
        "summary": "Cancel a booking",
        "description": "Staff are not bound by the cancellation policy and never charge a late fee. By default a paid booking is refunded in full and the customer emailed. Needs `booking:write` (assigned scope: bookings of sessions the member coaches); `credit=true` also needs `payment:write` (403 `forbidden`).",
        "operationId": "cancelStaffBooking",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "refund",
            "in": "query",
            "description": "Refund what was paid online",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true
            }
          },
          {
            "name": "refundPercentage",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 100,
              "maximum": 100,
              "minimum": 0
            }
          },
          {
            "name": "notify",
            "in": "query",
            "description": "Email the customer",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true
            }
          },
          {
            "name": "reason",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "credit",
            "in": "query",
            "description": "Give the refunded share back as account credit instead of a gateway refund (#752)",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "creditExpiresOn",
            "in": "query",
            "description": "Last day the credit can be used (local date); none = no expiry",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "booking": {
                      "$ref": "#/components/schemas/StaffBooking"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Cannot be cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "No payment:write for account credit (`forbidden`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookings": {
      "get": {
        "tags": [
          "Bookings"
        ],
        "summary": "List bookings",
        "description": "Paginated, filtered and sorted in the database. Needs `booking:read` (assigned scope: bookings of sessions the member coaches; members limited to locations: bookings there). Amounts, payment status and the `payment` filter and `price`/`outstanding` sorts need `payment:read`, else they are null or ignored.",
        "operationId": "listStaffBookings",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Name, email, booking reference or payment reference",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "`active` = pending or confirmed",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "pending",
                "confirmed",
                "cancelled",
                "expired"
              ]
            }
          },
          {
            "name": "participantId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "customerId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "sessionId",
            "in": "query",
            "description": "Also series bookings covering the session",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "seriesId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "dateFrom",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "dateTo",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "payment",
            "in": "query",
            "description": "payment:read only",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "outstanding",
                "unpaid",
                "partially_paid",
                "overdue"
              ]
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "bookedAt",
              "enum": [
                "bookedAt",
                "date",
                "participant",
                "status",
                "price",
                "outstanding"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "desc",
              "enum": [
                "asc",
                "desc"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bookings",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "bookings": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffBookingListItem"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/StaffBookingPagination"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid status, sort, date (YYYY-MM-DD) or id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookings/{id}/reschedule-options": {
      "get": {
        "tags": [
          "Bookings"
        ],
        "summary": "Where a booking can move to",
        "description": "Sessions and series of the same activity to come, with seats left and whether the price is the same, plus the participants it can be transferred to (#392). Needs `booking:write:all` (members limited to locations: options there).",
        "operationId": "getBookingRescheduleOptions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Options",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffBookingRescheduleOptions"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookings/{id}/reschedule": {
      "post": {
        "tags": [
          "Bookings"
        ],
        "summary": "Move a booking to another session or series",
        "description": "A session booking moves to another session, a series booking to another series, of the same activity (#392). Parties move with PUT /bookings/{id}/party, appointments are cancelled and booked again. Needs `booking:write:all`. 409 codes: `not_a_session`, `not_a_series`, `other_activity`, `not_bookable`, `already_booked`, `full`, `price_differs` (send allowPriceDifference), `party_not_supported`, `appointment`.",
        "operationId": "rescheduleStaffBooking",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "description": "Either sessionId or seriesId",
                "properties": {
                  "sessionId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "seriesId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "allowPriceDifference": {
                    "description": "Move to a session/series with another price; the booked price stays",
                    "type": "boolean",
                    "default": false
                  },
                  "notify": {
                    "description": "Email the customer",
                    "type": "boolean",
                    "default": true
                  },
                  "locale": {
                    "description": "Email language; default from Accept-Language",
                    "type": "string",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Moved",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "booking": {
                      "$ref": "#/components/schemas/StaffBookingMoved"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Send either sessionId or seriesId",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Not allowed; `code` says why",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookings/{id}/transfer": {
      "post": {
        "tags": [
          "Bookings"
        ],
        "summary": "Give a booking to another participant",
        "description": "Needs `booking:write:all`. 409 codes: `already_booked` (the participant already holds it), `party_not_supported`.",
        "operationId": "transferStaffBooking",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "participantId"
                ],
                "properties": {
                  "participantId": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Transferred",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "booking": {
                      "$ref": "#/components/schemas/StaffBookingMoved"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "participantId is required, or unknown participant",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Not allowed; `code` says why",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/booking-rules": {
      "get": {
        "tags": [
          "Organisation settings"
        ],
        "summary": "Booking rules",
        "description": "Attendance, booking window, absences, late sign-up, waiting list, checkout, fees, display and follow-up rules, plus session reminders. Needs organisation:read.",
        "operationId": "getBookingRules",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The rules",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "rules": {
                      "$ref": "#/components/schemas/OrgSettingsBookingRules"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Organisation settings"
        ],
        "summary": "Change the booking rules",
        "description": "Replaces the rules: send the whole object (sections left out take their defaults; reminders left out stay as they are). Needs organisation:write.",
        "operationId": "setBookingRules",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrgSettingsBookingRules"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The rules",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "rules": {
                      "$ref": "#/components/schemas/OrgSettingsBookingRules"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "A value out of range",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/public/{org_slug}/booking-window": {
      "get": {
        "tags": [
          "Public - Booking form"
        ],
        "summary": "When can this be booked?",
        "description": "The booking window of a session or series now (#401, #341; never who is in the priority audience), its age requirement (#352, null without one) and, with an optional `Authorization: Bearer` customer token, whether a spot from the waiting list is kept for that customer (`heldForYou`, `heldSeats`, a party's `heldTickets`; #684, #796). A seat of next term kept for them (#736) shows the window as open. 404 for an unknown item.",
        "operationId": "publicGetBookingWindow",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "sessionId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "seriesId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The state",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "bookingWindow": {
                      "$ref": "#/components/schemas/PublicFormBookingWindow"
                    },
                    "ageRequirement": {
                      "properties": {
                        "minMonths": {
                          "type": "integer",
                          "nullable": true
                        },
                        "maxMonths": {
                          "type": "integer",
                          "nullable": true
                        },
                        "enforcement": {
                          "type": "string"
                        },
                        "cutoff": {
                          "type": "string"
                        },
                        "referenceDate": {
                          "type": "string",
                          "format": "date"
                        }
                      },
                      "type": "object",
                      "nullable": true
                    },
                    "heldForYou": {
                      "type": "boolean"
                    },
                    "heldSeats": {
                      "type": "integer"
                    },
                    "heldTickets": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      },
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/org/{organisation_id}/series/{id}/booking-window": {
      "put": {
        "tags": [
          "Series"
        ],
        "summary": "Set the series' booking window",
        "description": "When the series opens and closes for online booking, with optional priority booking (#401, #341). An empty body or all nulls follows the organisation's booking rules. Needs series:write:all.",
        "operationId": "setSeriesBookingWindow",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SeriesSettingsBookingWindow"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The window set and the state now",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "bookingWindow": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/SeriesSettingsBookingWindow"
                        }
                      ],
                      "nullable": true
                    },
                    "state": {
                      "$ref": "#/components/schemas/SeriesSettingsBookingWindowState"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid dates or priority settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/booking-window": {
      "put": {
        "tags": [
          "Sessions"
        ],
        "summary": "Set a session's booking window",
        "description": "When the session opens for online booking, with optional earlier priority booking. An empty body or all nulls follows the series, then the organisation's booking rules. Needs session:write:all.",
        "operationId": "setSessionBookingWindow",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SessionBookingWindow"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The window and what it means now",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "bookingWindow": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/SessionBookingWindow"
                        }
                      ],
                      "nullable": true
                    },
                    "state": {
                      "properties": {
                        "status": {
                          "type": "string",
                          "enum": [
                            "open",
                            "priority",
                            "not_yet_open",
                            "closed"
                          ]
                        },
                        "opensAt": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true
                        },
                        "priorityOpensAt": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true
                        },
                        "closesAt": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid window (e.g. priority without a public opening date, priority opening after public, closesHoursBefore out of 0-1440)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookkeeping": {
      "get": {
        "tags": [
          "Bookkeeping"
        ],
        "summary": "The bookkeeping connection",
        "description": "The connection (null when none) and the software that can be connected. Credentials are never returned. Needs integration:read.",
        "operationId": "getBookkeeping",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Connection and providers",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "connection": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/BookkeepingConnection"
                        }
                      ],
                      "nullable": true
                    },
                    "providers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BookkeepingProvider"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Bookkeeping"
        ],
        "summary": "Change the key or settings",
        "description": "A new key and/or settings, tested before they are stored. Audit bookkeeping.updated. Errors (type): 404 not_connected, 422 connection_failed. Needs integration:write.",
        "operationId": "updateBookkeeping",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "credentials": {
                    "description": "Left out or empty: the stored key",
                    "type": "object"
                  },
                  "settings": {
                    "type": "object"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "connection": {
                      "$ref": "#/components/schemas/BookkeepingConnection"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Nothing connected (not_connected)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithType"
                }
              }
            }
          },
          "422": {
            "description": "Refused by the software (connection_failed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithType"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Bookkeeping"
        ],
        "summary": "Connect bookkeeping software with a key",
        "description": "Tests the connection first, then stores it (credentials encrypted, never returned). While connected, checkout offers billing details. Audit bookkeeping.connected. Errors (type): 409 already_connected, 422 connection_failed (the software refused it). Needs integration:write.",
        "operationId": "connectBookkeeping",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookkeepingConnectInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Connected",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "connection": {
                      "$ref": "#/components/schemas/BookkeepingConnection"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithType"
                }
              }
            }
          },
          "409": {
            "description": "Already connected (already_connected)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithType"
                }
              }
            }
          },
          "422": {
            "description": "Refused by the software (connection_failed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithType"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Bookkeeping"
        ],
        "summary": "Disconnect the bookkeeping software",
        "description": "The sync log goes; what was pushed stays in the software. Audit bookkeeping.disconnected. 404 not_connected. Needs integration:write.",
        "operationId": "disconnectBookkeeping",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "204": {
            "description": "Disconnected"
          },
          "404": {
            "description": "Nothing connected (not_connected)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithType"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookkeeping/oauth/start": {
      "post": {
        "tags": [
          "Bookkeeping"
        ],
        "summary": "Start connecting through the software's login",
        "description": "For software that connects with OAuth (Billit, #909): answers the login URL to send the browser to; the software returns to Plek, which redirects to the staff app with ?bookkeepingAuthorization={id}. Reconnects when the same software is connected. Errors (type): 409 already_connected (another software is connected), 422 oauth_not_configured. Staff sign-in only: the authorization belongs to the staff member. Needs integration:write.",
        "operationId": "startBookkeepingAuthorization",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "provider"
                ],
                "properties": {
                  "provider": {
                    "type": "string",
                    "enum": [
                      "billit"
                    ]
                  },
                  "syncFrom": {
                    "type": "string",
                    "format": "date",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Login to open",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "authorizationUrl": {
                      "type": "string",
                      "format": "uri"
                    },
                    "id": {
                      "type": "string"
                    },
                    "reconnect": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "409": {
            "description": "Another software is connected (already_connected)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithType"
                }
              }
            }
          },
          "422": {
            "description": "Unknown software or no OAuth client (oauth_not_configured)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithType"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookkeeping/oauth/{id}": {
      "get": {
        "tags": [
          "Bookkeeping"
        ],
        "summary": "A login that came back",
        "description": "After the software's login: the accounts (companies) the login reaches, to pick one. Only for the staff member who started it; 410 authorization_invalid when expired, used or someone else's. Needs integration:write.",
        "operationId": "getBookkeepingAuthorization",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Authorization id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Accounts to pick",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "provider": {
                      "type": "string",
                      "enum": [
                        "billit",
                        "yuki"
                      ]
                    },
                    "providerLabel": {
                      "type": "string"
                    },
                    "reconnect": {
                      "type": "boolean"
                    },
                    "accounts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BookkeepingAccount"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "410": {
            "description": "Expired, used or not yours (authorization_invalid)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithType"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookkeeping/oauth/{id}/complete": {
      "post": {
        "tags": [
          "Bookkeeping"
        ],
        "summary": "Connect the account picked",
        "description": "Connects the account picked (or the only one), or reconnects the existing connection (keeps its sync log; must be the same company). Audit bookkeeping.connected / bookkeeping.reconnected. Errors (type): 410 authorization_invalid, 422 account_required, other_account, connection_failed. Staff sign-in only. Needs integration:write.",
        "operationId": "completeBookkeepingAuthorization",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Authorization id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "accountId": {
                    "description": "The company picked; may be left out when the login reaches one",
                    "type": "string",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Connected",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "connection": {
                      "$ref": "#/components/schemas/BookkeepingConnection"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "410": {
            "description": "Expired or used (authorization_invalid)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithType"
                }
              }
            }
          },
          "422": {
            "description": "account_required, other_account or connection_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithType"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookkeeping/accounts": {
      "post": {
        "tags": [
          "Bookkeeping"
        ],
        "summary": "Accounts a key reaches",
        "description": "The accounts a key reaches (Yuki: administrations) for staff to pick one; nothing is stored. An empty key uses the stored one of the connected software. 422 connection_failed when refused, 400 for an unknown provider. Needs integration:write.",
        "operationId": "listBookkeepingAccounts",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "provider": {
                    "description": "Default: the connected software",
                    "type": "string",
                    "enum": [
                      "billit",
                      "yuki"
                    ]
                  },
                  "credentials": {
                    "description": "Yuki: {accessKey}; empty: the stored key",
                    "type": "object"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accounts",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "accounts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BookkeepingAccount"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Unknown or missing provider",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithType"
                }
              }
            }
          },
          "422": {
            "description": "Refused by the software (connection_failed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithType"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookkeeping/test": {
      "post": {
        "tags": [
          "Bookkeeping"
        ],
        "summary": "Test the connection",
        "description": "Asks the software whether the stored credentials still work. A failing test pauses pushing (status failing), a working one resumes it. Audit bookkeeping.connection_tested. 404 not_connected. Needs integration:write.",
        "operationId": "testBookkeeping",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string",
                      "nullable": true
                    },
                    "account": {
                      "description": "The account reached",
                      "type": "string",
                      "nullable": true
                    },
                    "connection": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/BookkeepingConnection"
                        }
                      ],
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Nothing connected (not_connected)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithType"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookkeeping/sync": {
      "post": {
        "tags": [
          "Bookkeeping"
        ],
        "summary": "Sync now",
        "description": "Queues a sync on the worker; with retryFailed the failed documents are tried again (audit bookkeeping.retried). 404 not_connected. Needs integration:write.",
        "operationId": "syncBookkeeping",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "retryFailed": {
                    "type": "boolean",
                    "default": false
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "queued": {
                      "type": "boolean"
                    },
                    "retried": {
                      "description": "Failed documents set to retry",
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Nothing connected (not_connected)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithType"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookkeeping/log": {
      "get": {
        "tags": [
          "Bookkeeping"
        ],
        "summary": "Sync log",
        "description": "Documents pushed or to push, newest first, 50 per page. Customers only with customer:read, amounts only with payment:read. 404 not_connected. Needs integration:read.",
        "operationId": "listBookkeepingLog",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "sent",
                "failed",
                "skipped"
              ]
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of documents",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "documents": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BookkeepingLogDocument"
                      }
                    },
                    "page": {
                      "type": "integer"
                    },
                    "hasMore": {
                      "type": "boolean"
                    },
                    "counts": {
                      "$ref": "#/components/schemas/BookkeepingCounts"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Nothing connected (not_connected)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithType"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/public/{org_slug}/calendar/{token}.ics": {
      "get": {
        "tags": [
          "Public - Calendar"
        ],
        "summary": "A private calendar feed",
        "description": "A subscribable feed (#403) by its secret token: a customer's bookings, or a staff member's sessions (`scope=all`: the whole schedule, for staff who may read every session). Whoever has the URL sees it; the owner revokes it by making a new one. 404 for an unknown or revoked token.",
        "operationId": "publicPrivateCalendarFeed",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "token",
            "in": "path",
            "description": "48 hex characters",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{48}$"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "description": "all: the whole schedule (staff feeds only)",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "iCalendar feed (ETag; 304 when unchanged)",
            "content": {
              "text/calendar": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Not modified (If-None-Match)"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/{org_slug}/calendar.ics": {
      "get": {
        "tags": [
          "Public - Calendar"
        ],
        "summary": "The public schedule as a calendar feed",
        "description": "The organisation's public schedule (#403) as a subscribable feed, optionally of one published activity or one location. 404 for an unknown organisation, activity or location.",
        "operationId": "publicScheduleCalendar",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "activityId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "iCalendar feed (ETag; 304 when unchanged)",
            "content": {
              "text/calendar": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Not modified (If-None-Match)"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/customer/{org_slug}/calendar-feed": {
      "get": {
        "tags": [
          "Customer - Calendar and push"
        ],
        "summary": "My calendar feed",
        "description": "The customer's subscribable feed of their bookings (#403) with subscribe links, or `feed: null` when they have none. Verified customers only.",
        "operationId": "customerGetCalendarFeed",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The feed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerCalendarFeedResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Customer - Calendar and push"
        ],
        "summary": "Make my calendar feed",
        "description": "Makes the customer's feed; when there is one already a new secret URL replaces it (the old URL stops working). Verified customers only.",
        "operationId": "customerProvideCalendarFeed",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The feed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerCalendarFeedResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Customer - Calendar and push"
        ],
        "summary": "Stop my calendar feed",
        "description": "Revokes the customer's feed: its URL stops working. Verified customers only.",
        "operationId": "customerRevokeCalendarFeed",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "204": {
            "description": "Revoked"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/me/calendar-feed": {
      "get": {
        "tags": [
          "Team"
        ],
        "summary": "My schedule as a calendar feed",
        "description": "The caller's private iCal feed of the sessions they coach (#403; null when not set up), plus the public schedule feed. Members with session:read:all also get a feed of the whole schedule. Needs session:read.",
        "operationId": "getMyCalendarFeed",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The feed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamCalendarFeed"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Team"
        ],
        "summary": "Set up or renew my calendar feed",
        "description": "Creates the feed, or gives the existing one a new URL (the old one then answers 404). Needs session:read.",
        "operationId": "createMyCalendarFeed",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The feed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamCalendarFeed"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Team"
        ],
        "summary": "Stop my calendar feed",
        "description": "Its URL stops working. Needs session:read.",
        "operationId": "revokeMyCalendarFeed",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "204": {
            "description": "Revoked"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/cancellation-policy": {
      "get": {
        "tags": [
          "Organisation settings"
        ],
        "summary": "The organisation's cancellation policy",
        "description": "The policy activities and series follow unless they override it (#391), and Plek's default. Needs organisation:read.",
        "operationId": "getOrganisationCancellationPolicy",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The policy",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "policy": {
                      "$ref": "#/components/schemas/SeriesSettingsCancellationPolicy"
                    },
                    "default": {
                      "$ref": "#/components/schemas/SeriesSettingsCancellationPolicy"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Organisation settings"
        ],
        "summary": "Set the organisation's cancellation policy",
        "description": "Fees (#406) included; {\"policy\": null} goes back to Plek's default. Needs organisation:write.",
        "operationId": "setOrganisationCancellationPolicy",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "policy"
                ],
                "properties": {
                  "policy": {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/SeriesSettingsCancellationPolicy"
                      }
                    ],
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The policy",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "policy": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/SeriesSettingsCancellationPolicy"
                        }
                      ],
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Body is not {policy} or the policy is invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/activities/{id}/cancellation-policy": {
      "put": {
        "tags": [
          "Activities"
        ],
        "summary": "Set the cancellation policy of an activity",
        "description": "Overrides the organisation policy for this activity (series may override it again); {\"policy\": null} follows the organisation again. Needs activity:write.",
        "operationId": "setActivityCancellationPolicy",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "policy": {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/ActivityCancellationPolicy"
                      }
                    ],
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The activity policy",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "policy": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/ActivityCancellationPolicy"
                        }
                      ],
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Body is not {policy} or the policy is invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/cancellation-policy": {
      "put": {
        "tags": [
          "Series"
        ],
        "summary": "Set the series' cancellation policy",
        "description": "The series' own cancellation policy, overriding the activity's and the organisation's (#391, fees #406); null removes it. Needs series:write.",
        "operationId": "setSeriesCancellationPolicy",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "policy"
                ],
                "properties": {
                  "policy": {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/SeriesSettingsCancellationPolicy"
                      }
                    ],
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The series' own policy (null: follows the activity or organisation)",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "policy": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/SeriesSettingsCancellationPolicy"
                        }
                      ],
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "No policy key, or an invalid policy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/public/{org_slug}/cancellation-policy": {
      "get": {
        "tags": [
          "Public - Booking form"
        ],
        "summary": "The cancellation policy that applies",
        "description": "The cancellation policy of a session or series (its own, else the activity's, else the organisation's, #391), or the organisation's without either id. A moment of a service booked at a free moment (#935), before it is booked: `slotOfferId` + `start` (the activity's policy, else the organisation's). With a session, series or moment, `freeUntil` is the moment free cancellation ends (#700). An id of another organisation falls back to the organisation's policy. 400 for a malformed id.",
        "operationId": "publicGetCancellationPolicy",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "sessionId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "seriesId",
            "in": "query",
            "description": "Used when no sessionId is given",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "slotOfferId",
            "in": "query",
            "description": "A moment of this slot offer (#935), with start; used when neither sessionId nor seriesId is given",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "start",
            "in": "query",
            "description": "The moment's start, ISO 8601 with offset (with slotOfferId)",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The policy",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "policy": {
                      "$ref": "#/components/schemas/SeriesSettingsCancellationPolicy"
                    },
                    "currency": {
                      "description": "The currency fixed fees are charged in: the session's or series' price currency, else the organisation's",
                      "type": "string"
                    },
                    "freeUntil": {
                      "description": "Free cancellation ends then; null without a session/series or without a free window",
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/{org_slug}/checkout/quote": {
      "post": {
        "tags": [
          "Public - Checkout"
        ],
        "summary": "What a checkout will cost",
        "description": "Per participant or party, with make-up credits and one discount code. A moment of a service booked at a free moment (#924) is quoted with `slot` instead of sessionId/seriesId: 409 {code: slot_taken} when it is no longer free. `participants[].entitlementId` (signed in) quotes a seat paid with a pass or subscription, also for a moment (#935).",
        "operationId": "quoteCheckout",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "sessionId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "seriesId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "slot": {
                    "$ref": "#/components/schemas/CheckoutSlot"
                  },
                  "participants": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "tickets": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "discountCode": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The quote",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "total": {
                      "type": "object"
                    },
                    "lines": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "409": {
            "description": "slot_taken",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/{org_slug}/checkout/customer": {
      "post": {
        "tags": [
          "Public - Checkout"
        ],
        "summary": "Booking step 1: the booker",
        "description": "Account-first booking (#422, #423): the booker's details before choosing participants. A new address creates an unverified customer and answers 201 with a `checkoutToken` (send it to /checkout, /waitlist) and no email yet: the review step asks for a code with POST /auth/code purpose `verify`. A known address answers 200 `existing` and emails a sign-in code; nothing about that account is shown or changed. With the `checkoutToken` of an account made earlier in this flow and still unverified, that account is updated (e.g. a corrected email) instead. Phone is required unless the organisation's booking rules make it optional; `customFields` are the organisation's customer fields (422 `details` `customFields.{name}`). Rate-limited per IP address and per address.",
        "operationId": "publicCheckoutCustomer",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "email",
                  "firstName",
                  "lastName"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "firstName": {
                    "type": "string"
                  },
                  "lastName": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string"
                  },
                  "customFields": {
                    "description": "Customer custom field name => value",
                    "type": "object"
                  },
                  "locale": {
                    "description": "Language of the sign-in code email",
                    "type": "string"
                  },
                  "checkoutToken": {
                    "description": "From an earlier answer of this endpoint",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`created` (own account updated) or `existing` (a sign-in code was emailed)",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "created",
                        "existing"
                      ]
                    },
                    "maskedEmail": {
                      "type": "string",
                      "example": "m•••@example.com"
                    },
                    "checkoutToken": {
                      "description": "Only when created",
                      "type": "string"
                    },
                    "resendAfter": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "201": {
            "description": "A new unverified account",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "created"
                    },
                    "maskedEmail": {
                      "type": "string"
                    },
                    "checkoutToken": {
                      "type": "string"
                    },
                    "resendAfter": {
                      "type": "integer",
                      "example": 0
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Validation failed: `details` per field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts (`rate_limited`, `retryAfter`, `Retry-After` header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/{org_slug}/checkout": {
      "post": {
        "tags": [
          "Public - Checkout"
        ],
        "summary": "Book",
        "description": "Books one or more participants (or a party) at a session, a series or, with `slot` (#924), a moment of a service booked at a free moment; account-first: an unknown customer gets an account. A moment: 409 {code: slot_taken | too_many_holds | not_available | appointment_only}, 422 {code: not_provider | invalid_slot}, 404 {code: unknown_offer}. A saved participant's `participants[].entitlementId` pays their seat with a pass, subscription or make-up credit covering the activity, a moment too (#935; GET /customer/{org}/entitlements?slotOfferId=&start= lists them): free and confirmed. Payment then goes through POST /bookings/{id}/payment.",
        "operationId": "placeCheckout",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "sessionId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "seriesId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "slot": {
                    "$ref": "#/components/schemas/CheckoutSlot"
                  },
                  "participants": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "tickets": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "customer": {
                    "type": "object"
                  },
                  "termsAccepted": {
                    "type": "boolean"
                  },
                  "discountCode": {
                    "type": "string"
                  },
                  "giftCardCodes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Booked",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "checkoutId": {
                      "type": "string"
                    },
                    "bookings": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "No longer available",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/{org_slug}/waitlist": {
      "post": {
        "tags": [
          "Public - Waitlist"
        ],
        "summary": "Join the waiting list",
        "description": "Joins the waiting list of a full session or series (#502), account-first like the checkout: signed in (optional `Authorization: Bearer` customer token; 401 `unauthenticated` when it is invalid), the account from /checkout/customer (`checkoutToken`), or a `customer` contact. Saved participants need a signed-in verified customer (403 `login_required` / `email_not_verified`). A headcount activity's session takes a party: `tickets` instead of `participants` (#796; participants are refused with 422 `headcount_only`). Emails a \"you are on the waiting list\" confirmation with the places. 409 `not_full` while a seat (or room for the party) is free, 409 `already_waiting` (with the `place` already held) / `already_booked` with `participantIndex`, 409 `not_available` when not on offer online, 404 `not_found`. Rate-limited per IP address.",
        "operationId": "publicJoinWaitlist",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "sessionId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "seriesId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "participants": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/PublicWaitlistParticipantInput"
                    }
                  },
                  "tickets": {
                    "description": "A party (sessions of a headcount activity only)",
                    "type": "array",
                    "items": {
                      "properties": {
                        "ticketTypeId": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "quantity": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "checkoutToken": {
                    "type": "string"
                  },
                  "customer": {
                    "$ref": "#/components/schemas/PublicCheckoutContactInput"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "On the waiting list",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "entries": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicWaitlistJoinedEntry"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "`unauthenticated`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "403": {
            "description": "`login_required` or `email_not_verified`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "409": {
            "description": "`not_full`, `already_waiting` (+ `place`), `already_booked` (+ `participantIndex`), `not_available`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "Validation failed or a ticket/participant refused (`details`, `code`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts (`rate_limited`, `retryAfter`, `Retry-After` header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/{org_slug}/interest": {
      "post": {
        "tags": [
          "Public - Waitlist"
        ],
        "summary": "Notify me when I can book",
        "description": "Registers a \"notify me when I can book\" request (#511) for exactly one of an activity (once it has bookable dates), a session or a series (once its booking window opens). Signed in (optional `Authorization: Bearer` customer token; 401 `unauthenticated` when invalid) or with a `customer` contact that creates an unverified account or attaches to the existing one without showing or changing it; the answer is the same either way. 409 `already_bookable` when it can be booked now, 404 `not_found`, 422 for missing fields. Rate-limited per IP address and per address.",
        "operationId": "publicRegisterInterest",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "activityId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "sessionId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "seriesId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "customer": {
                    "$ref": "#/components/schemas/PublicCheckoutContactInput"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registered",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "registered"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "`unauthenticated`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "409": {
            "description": "`already_bookable`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "Validation failed: `details` per field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts (`rate_limited`, `retryAfter`, `Retry-After` header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/{org_slug}/email-check": {
      "get": {
        "tags": [
          "Public - Checkout"
        ],
        "summary": "Can this address receive email?",
        "description": "Checks the domain of an address typed in the booking form (#342: catches typos like gmial.com). Fails open: `deliverable` is null when DNS cannot tell. 422 for something that is no address. Rate-limited per IP address.",
        "operationId": "publicEmailCheck",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "email",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "email"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The domain check",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "domain": {
                      "type": "string"
                    },
                    "deliverable": {
                      "type": "boolean",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Not an email address",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts (`rate_limited`, `retryAfter`, `Retry-After` header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        }
      }
    },
    "/api/org/{organisation_id}/closure-periods": {
      "get": {
        "tags": [
          "Closures"
        ],
        "summary": "Closure periods",
        "description": "All closures, or with locationId those that apply there (the organisation's and the location's own). Needs location:read.",
        "operationId": "listClosurePeriods",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Closures",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "closurePeriods": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffClosurePeriod"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid locationId",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Closures"
        ],
        "summary": "Add a closure period",
        "description": "Sessions generated later skip it. Scheduled sessions already inside it are listed in affectedSessions but NOT cancelled (see POST /closure-periods/cancel-sessions). Audit-logged (closure.created). Needs location:write.",
        "operationId": "createClosurePeriod",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffClosurePeriodInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "closurePeriod": {
                      "$ref": "#/components/schemas/StaffClosurePeriod"
                    },
                    "affectedSessions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffClosureAffectedSession"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "A field is missing, dates or timezone invalid, unknown location",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/closure-periods/{id}": {
      "get": {
        "tags": [
          "Closures"
        ],
        "summary": "One closure period",
        "description": "Needs location:read.",
        "operationId": "getClosurePeriod",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Closure id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The closure",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "closurePeriod": {
                      "$ref": "#/components/schemas/StaffClosurePeriod"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Closures"
        ],
        "summary": "Change a closure period",
        "description": "The whole closure again. Audit-logged (closure.updated). Needs location:write.",
        "operationId": "updateClosurePeriod",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Closure id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffClosurePeriodInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "closurePeriod": {
                      "$ref": "#/components/schemas/StaffClosurePeriod"
                    },
                    "affectedSessions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffClosureAffectedSession"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "A field is missing, dates or timezone invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Closures"
        ],
        "summary": "Remove a closure period",
        "description": "Sessions cancelled because of it stay cancelled. Audit-logged (closure.deleted). Needs location:write.",
        "operationId": "deleteClosurePeriod",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Closure id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/closure-periods/{id}/affected-sessions": {
      "get": {
        "tags": [
          "Closures"
        ],
        "summary": "Sessions inside a closure period",
        "description": "Scheduled sessions that fall inside it, with their active bookings counted, to pick which to cancel. Needs location:read.",
        "operationId": "listClosureAffectedSessions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Closure id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "timezone",
            "in": "query",
            "description": "Whose local days the closure dates are; default UTC",
            "required": false,
            "schema": {
              "type": "string",
              "example": "Europe/Brussels"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sessions",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "affectedSessions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffClosureAffectedSession"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid timezone",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/closure-periods/public-holidays": {
      "get": {
        "tags": [
          "Closures"
        ],
        "summary": "Public holidays of a country",
        "description": "Nationwide public holidays of BE, NL, FR or DE in a year, computed in code (Easter-based ones included), to add with POST /closure-periods/batch. key is a stable name the app translates. Needs location:read.",
        "operationId": "listPublicHolidays",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "country",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "BE",
                "NL",
                "FR",
                "DE"
              ]
            }
          },
          {
            "name": "year",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 2027
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Holidays",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "countries": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "holidays": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "key": {
                            "type": "string",
                            "example": "easter_monday"
                          },
                          "date": {
                            "type": "string",
                            "format": "date"
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Unknown country or invalid year",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/closure-periods/batch": {
      "post": {
        "tags": [
          "Closures"
        ],
        "summary": "Add several closure periods",
        "description": "E.g. a year of public holidays (#742), all for the organisation or one location; ones with the same dates and location as an existing closure are skipped. Sessions inside them are listed, not cancelled. Audit-logged. Needs location:write.",
        "operationId": "createClosurePeriods",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "closurePeriods"
                ],
                "properties": {
                  "closurePeriods": {
                    "type": "array",
                    "items": {
                      "required": [
                        "name",
                        "startDate",
                        "endDate"
                      ],
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "startDate": {
                          "type": "string",
                          "format": "date"
                        },
                        "endDate": {
                          "type": "string",
                          "format": "date"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "locationId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  },
                  "timezone": {
                    "type": "string",
                    "example": "Europe/Brussels"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "closurePeriods": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffClosurePeriod"
                      }
                    },
                    "skipped": {
                      "description": "Duplicates left out",
                      "type": "integer"
                    },
                    "affectedSessions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffClosureAffectedSession"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Not a list, a field is missing, invalid dates or timezone",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/closure-periods/cancel-sessions": {
      "post": {
        "tags": [
          "Closures"
        ],
        "summary": "Cancel the sessions inside closures",
        "description": "Cancels the picked sessions (each inside one of the closures) like the session batch cancel: notify (default true), cancelBookings, refund, or credit (account credit instead of a refund, payment:write; 403 otherwise), reason. addMakeUpSessions adds one make-up session per cancelled session at the end of its series (needs series:write:all, 403 otherwise). Audit-logged (closure.sessions_cancelled). Needs session:write; with the assigned scope only sessions the caller coaches.",
        "operationId": "cancelClosureSessions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "closurePeriodIds",
                  "sessionIds"
                ],
                "properties": {
                  "closurePeriodIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "sessionIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "notify": {
                    "type": "boolean",
                    "default": true
                  },
                  "cancelBookings": {
                    "type": "boolean",
                    "default": false
                  },
                  "refund": {
                    "type": "boolean",
                    "default": false
                  },
                  "credit": {
                    "description": "Give the refunded share back as account credit (#906)",
                    "type": "boolean",
                    "default": false
                  },
                  "creditExpiresOn": {
                    "type": "string",
                    "format": "date",
                    "nullable": true
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "addMakeUpSessions": {
                    "type": "boolean",
                    "default": false
                  },
                  "timezone": {
                    "type": "string",
                    "example": "Europe/Brussels"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Done",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "done": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "format": "uuid"
                      }
                    },
                    "skipped": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "sessionId": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "reason": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      }
                    },
                    "makeUp": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "seriesId": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "sessions": {
                            "type": "array",
                            "items": {
                              "properties": {
                                "id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "startTime": {
                                  "type": "string",
                                  "format": "date-time"
                                }
                              },
                              "type": "object"
                            }
                          },
                          "error": {
                            "description": "Why no make-up session could be added",
                            "type": "string"
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Ids missing, a session outside the closures, invalid reason",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Make-up sessions need series:write:all; credit needs payment:write",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/coaches/{user_id}/availability": {
      "get": {
        "tags": [
          "Coaches"
        ],
        "summary": "A coach's availability",
        "description": "The weekly pattern and the exceptions of the last 30 days and to come, time off with the sessions it clashes with (#741). Needs availability:read; with the assigned scope only one's own (404 otherwise).",
        "operationId": "getCoachAvailability",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "user_id",
            "in": "path",
            "description": "The coach's user id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Availability",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "coach": {
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "fullName": {
                          "type": "string"
                        },
                        "firstName": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "timezone": {
                      "type": "string",
                      "example": "Europe/Brussels"
                    },
                    "weekly": {
                      "$ref": "#/components/schemas/CoachWeeklyAvailability"
                    },
                    "updatedAt": {
                      "description": "Null: no pattern yet",
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    },
                    "exceptions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CoachAvailabilityException"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Coaches"
        ],
        "summary": "Set a coach's weekly pattern",
        "description": "Replaces the weekly pattern. 422 {code: invalid_pattern} for bad weekdays, times, overlapping or too many slots. Audit-logged. Needs availability:write; with the assigned scope only one's own.",
        "operationId": "setCoachWeeklyAvailability",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "weekly"
                ],
                "properties": {
                  "weekly": {
                    "$ref": "#/components/schemas/CoachWeeklyAvailability"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "weekly": {
                      "$ref": "#/components/schemas/CoachWeeklyAvailability"
                    },
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "invalid_pattern",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/coaches/{user_id}/availability/exceptions": {
      "post": {
        "tags": [
          "Coaches"
        ],
        "summary": "Add time off or extra availability",
        "description": "kind unavailable (time off, default) or available (extra availability), whole days or the same times on every day of the range. Time off answers its clashes: sessions the coach is down for, so they can ask for cover. 422 {code: invalid_exception}. Audit-logged. Needs availability:write; with the assigned scope only one's own.",
        "operationId": "addCoachAvailabilityException",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "startDate"
                ],
                "properties": {
                  "kind": {
                    "type": "string",
                    "default": "unavailable",
                    "enum": [
                      "unavailable",
                      "available"
                    ]
                  },
                  "startDate": {
                    "type": "string",
                    "format": "date"
                  },
                  "endDate": {
                    "description": "Default startDate",
                    "type": "string",
                    "format": "date"
                  },
                  "startTime": {
                    "description": "Both times or neither (whole days)",
                    "type": "string",
                    "example": "13:00",
                    "nullable": true
                  },
                  "endTime": {
                    "type": "string",
                    "example": "17:00",
                    "nullable": true
                  },
                  "note": {
                    "type": "string",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Added",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "exception": {
                      "$ref": "#/components/schemas/CoachAvailabilityException"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "invalid_exception",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/coaches/{user_id}/availability/exceptions/{exception_id}": {
      "delete": {
        "tags": [
          "Coaches"
        ],
        "summary": "Remove time off or extra availability",
        "description": "Audit-logged. Needs availability:write; with the assigned scope only one's own.",
        "operationId": "removeCoachAvailabilityException",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "exception_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Removed"
          },
          "404": {
            "description": "Coach or exception not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/coach-availability/check": {
      "post": {
        "tags": [
          "Coaches"
        ],
        "summary": "Check coaches against periods",
        "description": "One verdict per coach and period, e.g. for the session being edited: available, outside_pattern, unavailable (with the time off's note) or unknown (no pattern). Only ever a warning. At most 50 coaches and 400 periods. Needs availability:read:all.",
        "operationId": "checkCoachAvailability",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "coachIds",
                  "periods"
                ],
                "properties": {
                  "coachIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 50
                  },
                  "periods": {
                    "type": "array",
                    "items": {
                      "required": [
                        "start",
                        "end"
                      ],
                      "properties": {
                        "start": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "end": {
                          "type": "string",
                          "format": "date-time"
                        }
                      },
                      "type": "object"
                    },
                    "maxItems": 400
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verdicts, in the order of the periods",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "timezone": {
                      "type": "string"
                    },
                    "coaches": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "coachId": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "verdicts": {
                            "type": "array",
                            "items": {
                              "$ref": "#/components/schemas/CoachAvailabilityVerdict"
                            }
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid coachIds or periods",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/coach-availability": {
      "get": {
        "tags": [
          "Series"
        ],
        "summary": "Coach availability for the sessions of a series",
        "description": "The upcoming sessions of a series whose coaches are not available for them (time off, outside their weekly pattern or no pattern) (#741). Only ever a warning. Needs availability:read:all and series:read.",
        "operationId": "getSeriesCoachAvailability",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Problems",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "sessions": {
                      "description": "Upcoming sessions checked",
                      "type": "integer"
                    },
                    "problems": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "sessionId": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "startTime": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "endTime": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "coach": {
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "fullName": {
                                "type": "string",
                                "nullable": true
                              }
                            },
                            "type": "object"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "available",
                              "outside_pattern",
                              "unavailable",
                              "unknown"
                            ]
                          },
                          "note": {
                            "description": "Note of the time off",
                            "type": "string",
                            "nullable": true
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/coach-pay-rates": {
      "get": {
        "tags": [
          "Coaches"
        ],
        "summary": "Coach pay rates",
        "description": "Members whose role can coach, and anyone else with a rate, each with their rates oldest first (#741). Needs coach_pay:read (Admin by default).",
        "operationId": "listCoachPayRates",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Coaches with their rates",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "currency": {
                      "description": "The organisation's default currency",
                      "type": "string"
                    },
                    "coaches": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "fullName": {
                            "type": "string"
                          },
                          "email": {
                            "type": "string"
                          },
                          "role": {
                            "type": "string",
                            "nullable": true
                          },
                          "rates": {
                            "type": "array",
                            "items": {
                              "$ref": "#/components/schemas/CoachPayRate"
                            }
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/coaches/{user_id}/pay-rates": {
      "put": {
        "tags": [
          "Coaches"
        ],
        "summary": "Set a coach's pay rate from a date",
        "description": "Per hour, per session and per attendee checked in, in minor units of one currency (default the organisation's), from effectiveFrom on; a rate on the same date is replaced. 422 {code: invalid_rate}. Audit-logged (coach_pay_rate.*). Needs coach_pay:write.",
        "operationId": "setCoachPayRate",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "effectiveFrom"
                ],
                "properties": {
                  "effectiveFrom": {
                    "type": "string",
                    "format": "date"
                  },
                  "currency": {
                    "type": "string",
                    "example": "EUR",
                    "nullable": true
                  },
                  "perHour": {
                    "type": "integer",
                    "default": 0
                  },
                  "perSession": {
                    "type": "integer",
                    "default": 0
                  },
                  "perAttendee": {
                    "type": "integer",
                    "default": 0
                  },
                  "note": {
                    "type": "string",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "rate": {
                      "$ref": "#/components/schemas/CoachPayRate"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "invalid_rate",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/coach-pay-rates/{id}": {
      "delete": {
        "tags": [
          "Coaches"
        ],
        "summary": "Remove a pay rate",
        "description": "Audit-logged. Needs coach_pay:write.",
        "operationId": "deleteCoachPayRate",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Rate id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Removed"
          },
          "404": {
            "description": "Not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/substitutions": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "Coach substitution requests of a session",
        "description": "The history of \"I can't make it\" requests for the session. With the assigned scope only sessions the caller coaches. Needs session:read.",
        "operationId": "listSessionSubstitutions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Requests",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "substitutions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SessionCoachSubstitution"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Ask for a substitute coach",
        "description": "A coach says they can't make it (for themselves by default). Filing for another coach of the session needs session:write:all. Needs session:read; with the assigned scope only sessions the caller coaches.",
        "operationId": "requestSessionSubstitution",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "note": {
                    "type": "string",
                    "nullable": true
                  },
                  "coachId": {
                    "description": "Default: the caller",
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Requested",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "substitution": {
                      "$ref": "#/components/schemas/SessionCoachSubstitution"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid note or coachId, that person doesn't coach the session, the session isn't upcoming and scheduled, or a substitute was already asked for this coach",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Filing for someone else needs session:write:all",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/substitutions": {
      "get": {
        "tags": [
          "Coaches"
        ],
        "summary": "Open substitute requests",
        "description": "Coaches who can't make an upcoming session and still need cover (#601), with the session. Needs session:write:all.",
        "operationId": "listOpenSubstitutions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Requests",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "substitutions": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/SessionCoachSubstitution"
                          },
                          {
                            "properties": {
                              "session": {
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "startTime": {
                                    "type": "string",
                                    "format": "date-time"
                                  },
                                  "endTime": {
                                    "type": "string",
                                    "format": "date-time"
                                  },
                                  "activityName": {
                                    "type": "string"
                                  },
                                  "locationName": {
                                    "type": "string"
                                  }
                                },
                                "type": "object",
                                "nullable": true
                              }
                            },
                            "type": "object"
                          }
                        ]
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/substitutions/{id}/candidates": {
      "get": {
        "tags": [
          "Coaches"
        ],
        "summary": "Who could cover",
        "description": "Members whose role can coach, with their own overlapping sessions and their availability then (#741), available first. Needs session:write:all.",
        "operationId": "listSubstituteCandidates",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Substitution id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Candidates",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "candidates": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CoachSubstituteCandidate"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/substitutions/{id}/assign": {
      "post": {
        "tags": [
          "Coaches"
        ],
        "summary": "Assign a substitute coach",
        "description": "The substitute takes the coach's place on the session; notifyAttendees emails the attendees (in locale, default from Accept-Language). 409 {code: space_conflict, reason: appointments_booked} when the substitute has a booked appointment then. Needs session:write:all.",
        "operationId": "assignSubstituteCoach",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Substitution id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "substituteId"
                ],
                "properties": {
                  "substituteId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "notifyAttendees": {
                    "type": "boolean",
                    "default": false
                  },
                  "locale": {
                    "type": "string",
                    "enum": [
                      "en",
                      "nl",
                      "fr",
                      "de"
                    ]
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Assigned",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "substitution": {
                      "$ref": "#/components/schemas/SessionCoachSubstitution"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid substituteId, not a member who can coach, or the request is no longer open",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "space_conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionSpaceConflict"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/substitutions/{id}/close": {
      "post": {
        "tags": [
          "Coaches"
        ],
        "summary": "Withdraw or dismiss a substitute request",
        "description": "The coach withdraws their own request; dismissing someone else's needs session:write:all (404 otherwise). Needs session:read.",
        "operationId": "closeSubstitution",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Substitution id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Closed",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "substitution": {
                      "$ref": "#/components/schemas/SessionCoachSubstitution"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Not open any more",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/public/by-host/{hostname}": {
      "get": {
        "tags": [
          "Public - Organisation"
        ],
        "summary": "Which organisation an own domain belongs to",
        "description": "Internal: the customer app resolves its organisation from `location.hostname` when it runs on an organisation's own domain (#643). Only active domains; anything else is a 404. Cached for 5 minutes. Integrations know the organisation slug and have no use for it.",
        "operationId": "publicOrganisationByHost",
        "parameters": [
          {
            "name": "hostname",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "book.example.com"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The organisation",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "slug": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Unknown domain",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "x-internal": true
      }
    },
    "/api/org/{organisation_id}/custom-field-definitions": {
      "get": {
        "tags": [
          "Custom fields"
        ],
        "summary": "Custom fields",
        "description": "Paginated (page, per_page). Needs custom_field:read.",
        "operationId": "listCustomFields",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "entityType",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "customer",
                "participant",
                "booking"
              ]
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100,
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of fields",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "customFieldDefinitions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomFieldDefinition"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/StaffPageMetadata"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Unknown entityType",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Custom fields"
        ],
        "summary": "Add a custom field",
        "description": "entityType, fieldName, fieldType and label are required; a field name is unique per entity type. Needs custom_field:write.",
        "operationId": "createCustomField",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomFieldDefinitionInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "customFieldDefinition": {
                      "$ref": "#/components/schemas/CustomFieldDefinition"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid field, or the name exists",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/custom-field-definitions/{id}": {
      "get": {
        "tags": [
          "Custom fields"
        ],
        "summary": "One custom field",
        "description": "Needs custom_field:read.",
        "operationId": "getCustomField",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Custom field id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The field",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "customFieldDefinition": {
                      "$ref": "#/components/schemas/CustomFieldDefinition"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Custom fields"
        ],
        "summary": "Change a custom field",
        "description": "label is required; other fields left out stay as they are; entity type, name and type can't change. Needs custom_field:write.",
        "operationId": "updateCustomField",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Custom field id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomFieldDefinitionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "customFieldDefinition": {
                      "$ref": "#/components/schemas/CustomFieldDefinition"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing label or invalid value",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Custom fields"
        ],
        "summary": "Remove a custom field",
        "description": "Needs custom_field:write.",
        "operationId": "deleteCustomField",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Custom field id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/custom-field-definitions/templates": {
      "get": {
        "tags": [
          "Custom fields"
        ],
        "summary": "Custom-field templates",
        "description": "Preset bundles of fields (#750: emergency contact, pick-up, medical, ...), labelled in ?language= (else the organisation's default language); exists/added tell what the organisation has already. Needs custom_field:read.",
        "operationId": "listCustomFieldTemplates",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "language",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "en",
                "nl",
                "fr",
                "de"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Templates",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "language": {
                      "type": "string"
                    },
                    "templates": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomFieldTemplate"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/custom-field-definitions/templates/{key}": {
      "post": {
        "tags": [
          "Custom fields"
        ],
        "summary": "Add a template's fields",
        "description": "Creates the template's missing fields after the existing ones, labelled in language (else the default language); field names already there are skipped. 201 when something was created, else 200. Audit-logged (custom_field.template_applied). Needs custom_field:write.",
        "operationId": "applyCustomFieldTemplate",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "key",
            "in": "path",
            "description": "Template key, e.g. emergency_contact",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "language": {
                    "type": "string",
                    "enum": [
                      "en",
                      "nl",
                      "fr",
                      "de"
                    ]
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Nothing to add: every field exists"
          },
          "201": {
            "description": "Fields created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "created": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomFieldDefinition"
                      }
                    },
                    "skipped": {
                      "description": "Field names left alone",
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Unknown template (code unknown_template)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/bookings/{id}/sessions": {
      "get": {
        "tags": [
          "Customer - Bookings"
        ],
        "summary": "Sessions of my series booking",
        "description": "The sessions of a series booking with the participant's reported absences, their make-up credits and whether an absence can still be reported or withdrawn (#337), plus the organisation's absence rules. 404 for a booking that is not a series booking of the customer. Verified customers only.",
        "operationId": "customerSeriesBookingSessions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Booking id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The sessions",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "rules": {
                      "properties": {
                        "enabled": {
                          "type": "boolean"
                        },
                        "cutoffHours": {
                          "type": "integer"
                        },
                        "makeUpCredits": {
                          "type": "boolean"
                        },
                        "makeUpCreditWeeks": {
                          "type": "integer",
                          "nullable": true
                        }
                      },
                      "type": "object"
                    },
                    "sessions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerAbsenceSession"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/bookings/{id}/absences": {
      "post": {
        "tags": [
          "Customer - Bookings"
        ],
        "summary": "Report an absence",
        "description": "Reports that the participant of a series booking will miss one of its sessions (#337), before the organisation's cut-off; it may give a make-up credit. 404 for a session not in the booking; 409 with `code`: `disabled`, `too_late`, `session_cancelled`, `not_in_series`, `no_booking`. Verified customers only.",
        "operationId": "customerReportAbsence",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Booking id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "sessionId"
                ],
                "properties": {
                  "sessionId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "reason": {
                    "type": "string",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Reported",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "absence": {
                      "$ref": "#/components/schemas/CustomerAbsence"
                    },
                    "makeUpCredit": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CustomerAbsenceMakeUpCredit"
                        }
                      ],
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Not allowed (`code`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/bookings/{id}/absences/{session_id}": {
      "delete": {
        "tags": [
          "Customer - Bookings"
        ],
        "summary": "Withdraw a reported absence",
        "description": "The participant comes after all (#337): withdraws the absence and its make-up credit. 409 with `code`: `too_late`, `credit_used` (its make-up credit was already used), `seat_taken` (the seat was given away meanwhile), `disabled`. Verified customers only.",
        "operationId": "customerWithdrawAbsence",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Booking id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "session_id",
            "in": "path",
            "description": "Session id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Withdrawn"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Not allowed (`code`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/make-up-credits": {
      "get": {
        "tags": [
          "Customer - Bookings"
        ],
        "summary": "My make-up credits",
        "description": "Usable make-up credits of the customer's participants (#337), each with the participant's name and the activity. With `sessionId` only the ones that can pay for that session (book it at checkout with `participants[].entitlementId`). Verified customers only.",
        "operationId": "customerListMakeUpCredits",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "sessionId",
            "in": "query",
            "description": "Only credits that can pay for this session",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Credits",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "credits": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/CustomerAbsenceMakeUpCredit"
                          },
                          {
                            "properties": {
                              "participantName": {
                                "type": "string"
                              },
                              "activityName": {
                                "type": "string",
                                "nullable": true
                              }
                            },
                            "type": "object"
                          }
                        ]
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/me": {
      "get": {
        "tags": [
          "Customer - Account"
        ],
        "summary": "The signed-in customer",
        "description": "Profile of the customer the token belongs to. Unverified customers may call it too (`emailVerified` false).",
        "operationId": "customerGetMe",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The customer",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerAccountCustomerResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/profile": {
      "patch": {
        "tags": [
          "Customer - Account"
        ],
        "summary": "Change my profile",
        "description": "Name, phone, email language and opt-ins; fields left out stay as they are. The email address is not changed here: a different `email` is 403 `email_not_verified` for unverified customers and 422 `email_change_not_supported` otherwise (use POST /email-change). Turning text messages on needs a mobile number (422 `mobile_number_required`); an unknown language is 422.",
        "operationId": "customerUpdateProfile",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "firstName": {
                    "type": "string"
                  },
                  "lastName": {
                    "type": "string"
                  },
                  "phone": {
                    "description": "Stored in E.164 when valid; null clears it",
                    "type": "string",
                    "nullable": true
                  },
                  "language": {
                    "description": "The language emails are sent in",
                    "type": "string",
                    "enum": [
                      "en",
                      "nl",
                      "fr",
                      "de"
                    ]
                  },
                  "marketingOptIn": {
                    "type": "boolean"
                  },
                  "smsOptIn": {
                    "type": "boolean"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerAccountCustomerResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "422": {
            "description": "Invalid (`email_change_not_supported`, `mobile_number_required`, or a message)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/billing-details": {
      "get": {
        "tags": [
          "Customer - Account"
        ],
        "summary": "My company billing details",
        "description": "The company details used on receipts and sent to the organisation's bookkeeping (#791). Verified customers only.",
        "operationId": "customerGetBillingDetails",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Billing details, or null",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerAccountBillingDetailsResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Customer - Account"
        ],
        "summary": "Set or clear my company billing details",
        "description": "Replaces the stored details; `billingDetails: null` clears them. The VAT number is checked on format per EU country (422 `invalid_billing_details` with `details` per field). Verified customers only.",
        "operationId": "customerUpdateBillingDetails",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerAccountBillingDetailsResponse"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerAccountBillingDetailsResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "422": {
            "description": "Invalid billing details (`invalid_billing_details`)",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "example": "invalid_billing_details"
                    },
                    "details": {
                      "description": "Message per field",
                      "type": "object",
                      "additionalProperties": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/password": {
      "post": {
        "tags": [
          "Customer - Account"
        ],
        "summary": "Set or change my password",
        "description": "A password is optional for customers. Changing one needs `currentPassword` (403 when wrong); a weak or missing new password is 422. At most 10 attempts per 15 minutes (429 with Retry-After). Verified customers only.",
        "operationId": "customerChangePassword",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "newPassword"
                ],
                "properties": {
                  "currentPassword": {
                    "description": "Needed when the account has a password",
                    "type": "string",
                    "nullable": true
                  },
                  "newPassword": {
                    "type": "string",
                    "format": "password"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Saved"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Current password is incorrect, or `email_not_verified`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "Invalid new password",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/logout-all": {
      "post": {
        "tags": [
          "Customer - Account"
        ],
        "summary": "Sign out everywhere",
        "description": "Ends every session of this customer on every device (their refresh tokens stop working).",
        "operationId": "customerLogoutAll",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "204": {
            "description": "Signed out"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/public/{org_slug}/auth/code": {
      "post": {
        "tags": [
          "Public - Customer sign-in"
        ],
        "summary": "Email a sign-in code",
        "description": "Emails a 6-digit code and a sign-in link to the address. Answers 202 for every valid request, whether or not the address has an account at this organisation, so it never tells who is a customer. Purposes: `login` (default), `verify` (confirm the address of an account made by the booking form), `password_reset` (then POST /auth/password/reset). Rate-limited per IP address and per address, one code per 30 seconds per address. 422 for an invalid address or unknown purpose.",
        "operationId": "customerRequestSignInCode",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "purpose": {
                    "type": "string",
                    "default": "login",
                    "enum": [
                      "login",
                      "verify",
                      "password_reset"
                    ]
                  },
                  "locale": {
                    "description": "Language of the email (en, nl, fr, de); default: the request language",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Sent when the address has an account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAuthCodeSent"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid email address or purpose",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/PublicAuthTooManyAttempts"
          }
        }
      }
    },
    "/api/public/{org_slug}/auth/code/verify": {
      "post": {
        "tags": [
          "Public - Customer sign-in"
        ],
        "summary": "Sign in with an emailed code",
        "description": "Redeems the 6-digit code from POST /auth/code, POST /auth/register or the booking form and starts a session. Redeeming a code verifies the email address. 400 `invalid_code` for a wrong, used or expired code. Rate-limited per IP address and per address.",
        "operationId": "customerSignInWithCode",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "email",
                  "code"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "code": {
                    "type": "string",
                    "example": "123456"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAuthSession"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_code`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/PublicAuthTooManyAttempts"
          }
        }
      }
    },
    "/api/public/{org_slug}/auth/link": {
      "post": {
        "tags": [
          "Public - Customer sign-in"
        ],
        "summary": "Sign in with an emailed link",
        "description": "Redeems the token of an emailed sign-in link (magic link, \"Manage your booking\" in the confirmation email) and starts a session; verifies the email address. 400 `invalid_link` when it is invalid or expired. Rate-limited per IP address.",
        "operationId": "customerSignInWithLink",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "description": "The token from the emailed link",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAuthSession"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_link`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/PublicAuthTooManyAttempts"
          }
        }
      }
    },
    "/api/public/{org_slug}/auth/login": {
      "post": {
        "tags": [
          "Public - Customer sign-in"
        ],
        "summary": "Sign in with a password",
        "description": "For customers who chose a password (it is optional). 401 for a wrong address or password. Rate-limited per IP address and per address.",
        "operationId": "customerSignInWithPassword",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "email",
                  "password"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string",
                    "format": "password"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAuthSession"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/PublicAuthTooManyAttempts"
          }
        }
      }
    },
    "/api/public/{org_slug}/auth/register": {
      "post": {
        "tags": [
          "Public - Customer sign-in"
        ],
        "summary": "Create an account",
        "description": "Creates a customer account without booking and emails a code to confirm the address (an existing account gets a sign-in code instead). Answers 202 either way, so it never tells who is a customer; then sign in with POST /auth/code/verify. 403 `registration_closed` when the organisation only takes invited customers, 422 for invalid details. Rate-limited per IP address.",
        "operationId": "customerRegister",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "email",
                  "firstName",
                  "lastName"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "firstName": {
                    "type": "string"
                  },
                  "lastName": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string",
                    "nullable": true
                  },
                  "locale": {
                    "description": "Language of the email (en, nl, fr, de); default: the request language",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Check your inbox",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAuthCodeSent"
                }
              }
            }
          },
          "403": {
            "description": "`registration_closed`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/PublicAuthTooManyAttempts"
          }
        }
      }
    },
    "/api/public/{org_slug}/auth/password/reset": {
      "post": {
        "tags": [
          "Public - Customer sign-in"
        ],
        "summary": "Choose a new password with an emailed code",
        "description": "Takes a code requested with POST /auth/code purpose `password_reset`, sets the new password and starts a session. 400 `invalid_code`, 422 for an unacceptable password. Rate-limited per IP address and per address.",
        "operationId": "customerResetPassword",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "email",
                  "code",
                  "password"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "code": {
                    "type": "string"
                  },
                  "password": {
                    "type": "string",
                    "format": "password"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAuthSession"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_code`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid password",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/PublicAuthTooManyAttempts"
          }
        }
      }
    },
    "/api/public/{org_slug}/auth/refresh": {
      "post": {
        "tags": [
          "Public - Customer sign-in"
        ],
        "summary": "Renew the customer tokens",
        "description": "Exchanges a refresh token for a new access and refresh token; the old refresh token stops working. 400 without a refresh token, 401 when it is invalid or expired.",
        "operationId": "customerRefreshToken",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "refreshToken"
                ],
                "properties": {
                  "refreshToken": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "New tokens",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAuthTokens"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/public/{org_slug}/auth/logout": {
      "post": {
        "tags": [
          "Public - Customer sign-in"
        ],
        "summary": "Sign out",
        "description": "Ends the session of the refresh token. Always 204; an access token already handed out stays valid until it expires. Signing out everywhere: POST /api/customer/{org_slug}/logout-all.",
        "operationId": "customerSignOut",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "refreshToken": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Signed out"
          }
        }
      }
    },
    "/api/org/{organisation_id}/charges": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "Fees and charges",
        "description": "Late-cancellation and no-show fees (#406) and hire charges, newest first, of a booking, a customer or the whole organisation. Needs payment:read.",
        "operationId": "listCustomerCharges",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "customerId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "bookingId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "paid",
                "waived",
                "cancelled"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Charges",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "charges": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffCustomerCharge"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/charges/{id}/waive": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Waive a fee",
        "description": "An open fee, with a reason. Audit-logged. Errors (code): 404 not_found, 409 not_open, 422 reason_required, invalid. Needs payment:write.",
        "operationId": "waiveCustomerCharge",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Charge id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "reason"
                ],
                "properties": {
                  "reason": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Waived",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "charge": {
                      "$ref": "#/components/schemas/StaffCustomerCharge"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Fee not found (not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "409": {
            "description": "No longer open (not_open)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "reason_required or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/charges/{id}/mark-paid": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Record a fee as paid",
        "description": "Paid outside Plek. Audit-logged. Errors (code): 404 not_found, 409 not_open, 422 invalid (unknown method). Needs payment:write.",
        "operationId": "markCustomerChargePaid",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Charge id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "method": {
                    "type": "string",
                    "default": "other",
                    "enum": [
                      "cash",
                      "card",
                      "bank_transfer",
                      "other"
                    ]
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Paid",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "charge": {
                      "$ref": "#/components/schemas/StaffCustomerCharge"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Fee not found (not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "409": {
            "description": "No longer open (not_open)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "Unknown method (invalid)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/customers": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "List customers",
        "description": "Filtered, sorted and paginated in the database (#727). With the assigned scope only the contacts of attendees of my sessions, without national registry number. Plans need entitlement:read and fees/outstanding payment:read, else those filters are ignored and the fields null. Needs customer:read.",
        "operationId": "listCustomers",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page, from 1",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Items per page",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Name, email or phone",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "verified",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "yes",
                "no"
              ]
            }
          },
          {
            "name": "plan",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "none"
              ]
            }
          },
          {
            "name": "planExpiresWithin",
            "in": "query",
            "description": "Days",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "noBookingFor",
            "in": "query",
            "description": "Days: no booked session in the last N days and none ahead",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "fees",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "none"
              ]
            }
          },
          {
            "name": "outstanding",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "yes",
                "no"
              ]
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "name",
                "email",
                "createdAt",
                "lastBooking",
                "nextBooking",
                "planExpiry"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "ids",
            "in": "query",
            "description": "Comma-separated customer ids",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tags",
            "in": "query",
            "description": "Comma-separated tag ids: customers carrying at least one",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Customers",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "customers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffCustomerListItem"
                      }
                    },
                    "visible": {
                      "description": "Whether plans and fees are shown to this member",
                      "properties": {
                        "plans": {
                          "type": "boolean"
                        },
                        "fees": {
                          "type": "boolean"
                        }
                      },
                      "type": "object"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/StaffPageMetadata"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid filter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Create a customer",
        "description": "A customer account of this organisation, unique on email. Needs customer:write.",
        "operationId": "createCustomer",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "email",
                  "firstName",
                  "lastName"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "firstName": {
                    "type": "string"
                  },
                  "lastName": {
                    "type": "string"
                  },
                  "nationalRegistryNumber": {
                    "type": "string",
                    "nullable": true
                  },
                  "phone": {
                    "type": "string",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "customer": {
                      "$ref": "#/components/schemas/StaffCustomerBasic"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing field or invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/customers/{id}": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "One customer",
        "description": "With tags and participants. With the assigned scope only contacts of my attendees (else 404), only my attendees as participants, and no national registry number or billing details. Needs customer:read.",
        "operationId": "getCustomer",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Customer id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The customer",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "customer": {
                      "$ref": "#/components/schemas/StaffCustomerDetail"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid customer id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Customers"
        ],
        "summary": "Change a customer",
        "description": "The email is changed with POST /customers/{id}/email. billingDetails left out stays, null clears it. Needs customer:write.",
        "operationId": "updateCustomer",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Customer id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "firstName",
                  "lastName"
                ],
                "properties": {
                  "firstName": {
                    "type": "string"
                  },
                  "lastName": {
                    "type": "string"
                  },
                  "nationalRegistryNumber": {
                    "type": "string",
                    "nullable": true
                  },
                  "phone": {
                    "type": "string",
                    "nullable": true
                  },
                  "billingDetails": {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/StaffBillingDetails"
                      }
                    ],
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "customer": {
                      "$ref": "#/components/schemas/StaffCustomerBasic"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing field or invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid billing details (code invalid_billing_details, details per field)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/customers/to-check": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Customers whose email needs checking",
        "description": "Email bounced or still unverified 48 h after booking something upcoming (#425), with the bounce rate of the last 30 days. Needs customer:write:all.",
        "operationId": "listCustomersToCheck",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page, from 1",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Items per page",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Customers to check",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "customers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffCustomerToCheck"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/StaffPageMetadata"
                    },
                    "stats": {
                      "properties": {
                        "periodDays": {
                          "type": "integer"
                        },
                        "customersCreated": {
                          "type": "integer"
                        },
                        "undeliverable": {
                          "type": "integer"
                        },
                        "bounceRate": {
                          "description": "Fraction, 0..1",
                          "type": "number"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/customers/{id}/email": {
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Correct a customer email",
        "description": "Sets the new address and by default sends the booking confirmation again (#425). Needs customer:write:all.",
        "operationId": "correctCustomerEmail",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Customer id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "resendConfirmation": {
                    "type": "boolean",
                    "default": true
                  },
                  "locale": {
                    "description": "Email language, default the request language",
                    "type": "string",
                    "enum": [
                      "en",
                      "nl",
                      "fr",
                      "de"
                    ]
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The customer",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "customer": {
                      "$ref": "#/components/schemas/StaffCustomer"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid email (code invalid_email)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Another customer has this email (code email_taken)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/customers/{id}/resend-confirmation": {
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Send the confirmation again",
        "description": "Queues the booking confirmation (or a verification code) again (#425). Needs customer:write:all.",
        "operationId": "resendCustomerConfirmation",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Customer id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "locale": {
                    "description": "Email language, default the request language",
                    "type": "string",
                    "enum": [
                      "en",
                      "nl",
                      "fr",
                      "de"
                    ]
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "queued": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/customers/{id}/timeline": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Customer history",
        "description": "Bookings, payments, plans, waiting list, attendance, emails and notes, newest first (#605). Each group needs its own read:all permission (booking, payment, entitlement or plan, waitlist, attendance); groups lists the ones shown. Needs customer:read:all.",
        "operationId": "getCustomerTimeline",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Customer id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page, from 1",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Items per page",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "types",
            "in": "query",
            "description": "Comma-separated groups: bookings, payments, plans, waitlist, attendance, emails, notes",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Timeline",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "entries": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffCustomerTimelineEntry"
                      }
                    },
                    "groups": {
                      "description": "The groups this member may see",
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/StaffPageMetadata"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/customers/{id}/notes": {
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Add a note",
        "description": "An internal note on the customer timeline. Needs customer:write.",
        "operationId": "addCustomerNote",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Customer id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "body"
                ],
                "properties": {
                  "body": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "note": {
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "body": {
                          "type": "string"
                        },
                        "createdAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "author": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Empty or invalid note",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/customers/{id}/notes/{note_id}": {
      "delete": {
        "tags": [
          "Customers"
        ],
        "summary": "Delete a note",
        "description": "Needs customer:write.",
        "operationId": "deleteCustomerNote",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Customer id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "note_id",
            "in": "path",
            "description": "Note id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/customers/{id}/tasks": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Tasks about a customer",
        "description": "Needs customer:read:all.",
        "operationId": "listCustomerTasks",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Customer id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tasks",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "tasks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffCustomerTask"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Add a task",
        "description": "A follow-up task, optionally assigned to a member. Needs customer:write.",
        "operationId": "createCustomerTask",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Customer id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "title"
                ],
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "dueDate": {
                    "type": "string",
                    "format": "date",
                    "nullable": true
                  },
                  "assigneeUserId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "task": {
                      "$ref": "#/components/schemas/StaffCustomerTask"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid task",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/customers/{id}/tasks/{task_id}": {
      "put": {
        "tags": [
          "Customers"
        ],
        "summary": "Change a task",
        "description": "Fields left out stay as they are. Needs customer:write.",
        "operationId": "updateCustomerTask",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Customer id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "task_id",
            "in": "path",
            "description": "Task id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "dueDate": {
                    "type": "string",
                    "format": "date",
                    "nullable": true
                  },
                  "assigneeUserId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  },
                  "done": {
                    "type": "boolean"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "task": {
                      "$ref": "#/components/schemas/StaffCustomerTask"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid task",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Customers"
        ],
        "summary": "Delete a task",
        "description": "Needs customer:write.",
        "operationId": "deleteCustomerTask",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Customer id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "task_id",
            "in": "path",
            "description": "Task id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/customer-tasks/mine": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "My open customer tasks",
        "description": "Open tasks assigned to the caller, at most 50, with the customer's name (#605). Needs customer:read.",
        "operationId": "listMyCustomerTasks",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Tasks",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "tasks": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/StaffCustomerTask"
                          },
                          {
                            "properties": {
                              "customerName": {
                                "type": "string",
                                "nullable": true
                              }
                            },
                            "type": "object"
                          }
                        ]
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/customer-tasks/assignees": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Who tasks can be assigned to",
        "description": "The organisation's members by name. Needs customer:write.",
        "operationId": "listCustomerTaskAssignees",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Members",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "assignees": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "description": "User id",
                            "type": "string",
                            "format": "uuid"
                          },
                          "fullName": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/bookings/{id}/pay": {
      "post": {
        "tags": [
          "Customer - Bookings"
        ],
        "summary": "Pay what is left online",
        "description": "For a booking paid later (on site or by bank transfer, #723): starts an online payment of what is still outstanding; a paid online payment settles it in full. `returnUrl` must be a page the organisation allows, else the booking site is used. Send the browser to `checkoutUrl`. 409 `not_payable` (nothing left to pay, or not payable online), 502 `payment_failed`. Verified customers only.",
        "operationId": "customerPayBookingBalance",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Booking id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "returnUrl": {
                    "type": "string",
                    "format": "uri"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Payment started",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "checkoutUrl": {
                      "type": "string",
                      "format": "uri"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`not_payable`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "502": {
            "description": "`payment_failed`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/bookings": {
      "get": {
        "tags": [
          "Customer - Bookings"
        ],
        "summary": "My bookings",
        "description": "The signed-in customer's bookings (of all their participants and parties), paginated. `when=upcoming`: not ended yet, soonest first; `when=past`: the rest, most recent first; without it all, newest booking first. `status=active` leaves out cancelled and expired bookings. `meta.awaitingPayment` counts unpaid bookings still holding a place. Verified customers only.",
        "operationId": "customerListBookings",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "when",
            "in": "query",
            "description": "upcoming or past",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "upcoming",
                "past"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "active: only pending and confirmed bookings",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "active"
              ]
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100,
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bookings",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerBooking"
                      }
                    },
                    "meta": {
                      "properties": {
                        "total": {
                          "type": "integer"
                        },
                        "page": {
                          "type": "integer"
                        },
                        "limit": {
                          "type": "integer"
                        },
                        "pages": {
                          "type": "integer"
                        },
                        "awaitingPayment": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/bookings/{id}/receipt.pdf": {
      "get": {
        "tags": [
          "Customer - Bookings"
        ],
        "summary": "Receipt of a booking (PDF)",
        "description": "A payment confirmation of one of the customer's bookings (#891; not an invoice), in the customer's language. Available when the booking detail says `receiptAvailable`; 404 otherwise. Verified customers only.",
        "operationId": "customerBookingReceipt",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Booking id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The receipt",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/bookings/{id}": {
      "get": {
        "tags": [
          "Customer - Bookings"
        ],
        "summary": "One of my bookings",
        "description": "One booking of the customer, with the coaches' first names and the \"Good to know\" notes of the activity and location (#704); an appointment only shows the names its staff show on the booking site. 404 for a booking of someone else. Verified customers only.",
        "operationId": "customerGetBooking",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Booking id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The booking",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "booking": {
                      "$ref": "#/components/schemas/CustomerBookingDetail"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Customer - Bookings"
        ],
        "summary": "Cancel my booking",
        "description": "Cancels the booking as the customer; the cancellation policy decides whether that is allowed and what is refunded (#391), and a late-cancellation fee may be charged (#406). 409 `cancellation_not_allowed` with the policy `cancellation` decision, 400 when already cancelled or not cancellable. Verified customers only.",
        "operationId": "customerCancelBooking",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Booking id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "booking": {
                      "$ref": "#/components/schemas/CustomerBooking"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Already cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`cancellation_not_allowed`",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "example": "cancellation_not_allowed"
                    },
                    "cancellation": {
                      "$ref": "#/components/schemas/CustomerBookingCancellation"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/bookings/{id}/reschedule-options": {
      "get": {
        "tags": [
          "Customer - Bookings"
        ],
        "summary": "Where can I move this booking?",
        "description": "Whether the customer may move this session booking to another session themselves (#392, within the cancellation policy's window and limit) and, if so, the sessions it can move to. Verified customers only.",
        "operationId": "customerBookingRescheduleOptions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Booking id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The decision and the sessions",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "reschedule": {
                      "$ref": "#/components/schemas/CustomerBookingRescheduleDecision"
                    },
                    "sessions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerBookingRescheduleOption"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/bookings/{id}/reschedule": {
      "post": {
        "tags": [
          "Customer - Bookings"
        ],
        "summary": "Move my booking to another session",
        "description": "Moves the booking to one of the sessions of GET reschedule-options and emails the customer. 400 without a valid `sessionId`; 409 with `code` (e.g. `window_passed`, `limit_reached`, `full`, `already_booked`, `price_differs`, `other_activity`, `not_bookable`, `appointment`, `party_not_supported`). Verified customers only.",
        "operationId": "customerRescheduleBooking",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Booking id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "sessionId"
                ],
                "properties": {
                  "sessionId": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Moved",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "booking": {
                      "$ref": "#/components/schemas/CustomerBooking"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Not allowed (`code`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/email-change": {
      "post": {
        "tags": [
          "Customer - Account"
        ],
        "summary": "Ask to change my email address",
        "description": "Emails a 6-digit code to the new address (purpose `email_change`); the account keeps its old address until the code is confirmed with POST /email-change/verify. Errors: 422 `invalid_email` / `same_email`, 409 `email_taken` (another account of this organisation uses it), 429 after 5 requests an hour. Verified customers only.",
        "operationId": "customerRequestEmailChange",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 255
                  },
                  "locale": {
                    "description": "Language of the email; default the customer's language",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Code sent",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "email": {
                      "type": "string",
                      "format": "email"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "409": {
            "description": "Address in use (`email_taken`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "Invalid (`invalid_email`, `same_email`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts (`retryAfter` seconds)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/email-change/verify": {
      "post": {
        "tags": [
          "Customer - Account"
        ],
        "summary": "Confirm my new email address",
        "description": "Changes the address with the code sent to it; the old address gets a notice (audit `customer.email_changed`). Never signs in. Errors: 400 `invalid_code` (wrong or expired), 409 `email_taken`. Verified customers only.",
        "operationId": "customerVerifyEmailChange",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "example": "123456"
                  },
                  "locale": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Changed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerAccountCustomerResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid or expired code (`invalid_code`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "409": {
            "description": "Address in use (`email_taken`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/entitlements": {
      "get": {
        "tags": [
          "Customer - Plans and credit"
        ],
        "summary": "My passes, subscriptions, perks and make-up credits",
        "description": "What the customer and their participants hold (#374): make-up credits, subscriptions, passes and series perks (granted when looked at), with balances, usage history and the receipt of a plan bought online. With `sessionId`, or `slotOfferId` + `start` for a moment of a service booked at a free moment (#935): per participant whether each one covers it (the checkout uses this; pay with `participants[].entitlementId`). Verified customers only.",
        "operationId": "customerListEntitlements",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "sessionId",
            "in": "query",
            "description": "Check what covers this session",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "slotOfferId",
            "in": "query",
            "description": "Check what covers a moment of this slot offer (with start)",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "start",
            "in": "query",
            "description": "The moment's start as GET /activities/{id}/slots returns it (with slotOfferId)",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Entitlements",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "entitlements": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerEntitlement"
                      }
                    },
                    "awaitingPayment": {
                      "description": "Plans bought to pay on site or by bank transfer, granted once staff record the payment (#607); with sessionId only the ones that would cover it, without offlinePayment",
                      "type": "array",
                      "items": {
                        "properties": {
                          "purchase": {
                            "$ref": "#/components/schemas/CustomerPlanPurchase"
                          },
                          "offlinePayment": {
                            "oneOf": [
                              {
                                "$ref": "#/components/schemas/CustomerPlanPurchaseOfflinePayment"
                              }
                            ],
                            "nullable": true
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/hires/{id}/calendar.ics": {
      "get": {
        "tags": [
          "Customer - Hires"
        ],
        "summary": "My confirmed hire as a calendar file",
        "description": "An .ics file of a confirmed hire of the customer (#838). 422 `invalid_status` when it is not confirmed, 404 for someone else's. Verified customers only.",
        "operationId": "customerHireCalendar",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Hire id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "iCalendar file",
            "content": {
              "text/calendar": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerHireError"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_status`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerHireError"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/hires": {
      "get": {
        "tags": [
          "Customer - Hires"
        ],
        "summary": "My venue hires",
        "description": "The customer's venue hire requests (#794) in every status. Verified customers only.",
        "operationId": "customerListHires",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Hires",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "hires": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerHire"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/hires/{id}": {
      "get": {
        "tags": [
          "Customer - Hires"
        ],
        "summary": "One of my venue hires",
        "description": "A hire of the customer: the party, the quote once sent, open amounts, how to pay and the cancellation deadline. 404 for someone else's. Verified customers only.",
        "operationId": "customerGetHire",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Hire id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The hire",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "hire": {
                      "$ref": "#/components/schemas/CustomerHire"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerHireError"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/hires/{id}/accept": {
      "post": {
        "tags": [
          "Customer - Hires"
        ],
        "summary": "Accept the quote",
        "description": "Accepts a quoted hire: takes its spaces (shared sessions close) and adds the deposit. Paid online (default), the deposit payment starts at once (`checkoutUrl`; it can be retried from Open amounts); on site or by bank transfer the deposit is due by its deadline. 409 `space_conflict` when another hire, block or booked session took a space meanwhile (of two quotes for one space only the first wins); 422 `invalid_status`, `quote_expired`, `payment_method_unavailable`. Verified customers only.",
        "operationId": "customerAcceptHire",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Hire id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "paymentMethod": {
                    "type": "string",
                    "default": "online",
                    "enum": [
                      "online",
                      "on_site",
                      "bank_transfer"
                    ]
                  },
                  "returnUrl": {
                    "description": "Page to come back to after paying the deposit online",
                    "type": "string",
                    "format": "uri"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accepted",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "hire": {
                      "$ref": "#/components/schemas/CustomerHire"
                    },
                    "checkoutUrl": {
                      "type": "string",
                      "format": "uri",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerHireError"
                }
              }
            }
          },
          "409": {
            "description": "`space_conflict`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerHireError"
                }
              }
            }
          },
          "422": {
            "description": "Not possible (`code`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerHireError"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/hires/{id}/decline": {
      "post": {
        "tags": [
          "Customer - Hires"
        ],
        "summary": "Decline the quote",
        "description": "Declines a quoted hire, with an optional reason for the organisation. 422 `invalid_status`. Verified customers only.",
        "operationId": "customerDeclineHire",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Hire id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "reason": {
                    "type": "string",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Declined",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "hire": {
                      "$ref": "#/components/schemas/CustomerHire"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerHireError"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_status`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerHireError"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/hires/{id}/cancel": {
      "post": {
        "tags": [
          "Customer - Hires"
        ],
        "summary": "Cancel my hire",
        "description": "Cancels an accepted or confirmed hire and frees its spaces. Before the cancellation deadline (the activity's free-cancellation hours before the event) a deposit paid online is refunded (`refunds`), one paid offline is owed back; after it the deposit is kept. 422 `invalid_status`, `cancellation_closed`. Verified customers only.",
        "operationId": "customerCancelHire",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Hire id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "reason": {
                    "type": "string",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "hire": {
                      "$ref": "#/components/schemas/CustomerHire"
                    },
                    "refunds": {
                      "description": "Refunds made through the payment provider",
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerHireError"
                }
              }
            }
          },
          "422": {
            "description": "Not possible (`code`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerHireError"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/hires/{id}/charges/{charge_id}/receipt.pdf": {
      "get": {
        "tags": [
          "Customer - Hires"
        ],
        "summary": "Receipt of a hire payment (PDF)",
        "description": "A payment confirmation of a paid deposit, balance or extra of the customer's own hire (#891; not an invoice). 404 for another hire's charge or an unpaid one. Verified customers only.",
        "operationId": "customerHireChargeReceipt",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Hire id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "charge_id",
            "in": "path",
            "description": "Charge id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The receipt",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerHireError"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/interest": {
      "get": {
        "tags": [
          "Customer - Waitlist and interest"
        ],
        "summary": "My \"notify me\" requests",
        "description": "The customer's pending \"notify me when I can book\" requests (#511, #565). Verified customers only.",
        "operationId": "customerListInterest",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Requests",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "requests": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerInterestRequest"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/interest/{id}": {
      "delete": {
        "tags": [
          "Customer - Waitlist and interest"
        ],
        "summary": "Cancel a \"notify me\" request",
        "description": "Stops a pending request of the customer. 404 for an unknown request or someone else's. Verified customers only.",
        "operationId": "customerCancelInterest",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Request id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Cancelled"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/public/{org_slug}/auth/oidc/providers": {
      "get": {
        "tags": [
          "Public - Customer sign-in"
        ],
        "summary": "External sign-in providers",
        "description": "The external sign-in providers (Google, Microsoft) configured on this Plek installation, so a page only shows buttons that work.",
        "operationId": "customerListSignInProviders",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Providers",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "providers": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "example": "google"
                          },
                          "name": {
                            "type": "string",
                            "example": "Google"
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/{org_slug}/auth/oidc/{provider}/start": {
      "post": {
        "tags": [
          "Public - Customer sign-in"
        ],
        "summary": "Start signing in with Google or Microsoft",
        "description": "Step 1 of external sign-in: the page keeps a random verifier and sends its SHA-256 (`clientChallenge`) and its own URL (`returnUrl`, an allowed website of the organisation, its booking site or the customer app). Send the browser to `authorizationUrl`; the provider returns to the API, which sends the browser back to `returnUrl` with `#plek_sso=<ticket>` (or `#plek_sso_error=<reason>`). Then POST /auth/oidc/redeem. 404 for a provider that is not configured, 422 for a return URL that is not allowed or a malformed challenge, 502 `external_sign_in_failed`. Rate-limited per IP address.",
        "operationId": "customerStartExternalSignIn",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "google"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "returnUrl",
                  "clientChallenge"
                ],
                "properties": {
                  "returnUrl": {
                    "type": "string",
                    "format": "uri"
                  },
                  "clientChallenge": {
                    "description": "SHA-256 of the page verifier (PKCE S256)",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Where to send the browser",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "authorizationUrl": {
                      "type": "string",
                      "format": "uri"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Return URL not allowed or malformed challenge",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/PublicAuthTooManyAttempts"
          },
          "502": {
            "description": "`external_sign_in_failed`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/{org_slug}/auth/oidc/redeem": {
      "post": {
        "tags": [
          "Public - Customer sign-in"
        ],
        "summary": "Finish signing in with Google or Microsoft",
        "description": "Step 2 of external sign-in: redeems the ticket from `#plek_sso=` with the verifier kept by the page and starts a session (finding or creating the customer by the verified address of the provider). 400 `external_sign_in_failed` for an invalid, used or expired ticket or a wrong verifier. Rate-limited per IP address.",
        "operationId": "customerRedeemExternalSignIn",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "ticket",
                  "verifier"
                ],
                "properties": {
                  "ticket": {
                    "type": "string"
                  },
                  "verifier": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAuthSession"
                }
              }
            }
          },
          "400": {
            "description": "`external_sign_in_failed`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/PublicAuthTooManyAttempts"
          }
        }
      }
    },
    "/api/customer/{org_slug}/charges": {
      "get": {
        "tags": [
          "Customer - Open amounts"
        ],
        "summary": "My open amounts",
        "description": "Late-cancellation and no-show fees (#406) and venue hire charges (#794) of the customer, with the open total (in the organisation's currency, else the first one; `openTotals` per currency, never added up across currencies), whether one is overdue, whether online booking is blocked by an overdue fee and whether it can be paid online. Verified customers only.",
        "operationId": "customerListCharges",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Charges",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "charges": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerCharge"
                      }
                    },
                    "openTotal": {
                      "$ref": "#/components/schemas/CustomerAmount"
                    },
                    "openTotals": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerAmount"
                      }
                    },
                    "openCount": {
                      "type": "integer"
                    },
                    "overdue": {
                      "type": "boolean"
                    },
                    "bookingBlocked": {
                      "type": "boolean"
                    },
                    "canPayOnline": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/charges/{id}": {
      "get": {
        "tags": [
          "Customer - Open amounts"
        ],
        "summary": "One open amount",
        "description": "A charge of the customer; an open one with a payment under way is checked with the payment provider first (the return page after paying). 404 for someone else's. Verified customers only.",
        "operationId": "customerGetCharge",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Charge id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The charge",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "charge": {
                      "$ref": "#/components/schemas/CustomerCharge"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/charges/{id}/pay": {
      "post": {
        "tags": [
          "Customer - Open amounts"
        ],
        "summary": "Pay an open amount online",
        "description": "Starts an online payment of an open charge; send the browser to `checkoutUrl`. `returnUrl` must be a page the organisation allows, else Open amounts on the booking site. 404 `not_found`; 409 `not_open`, `no_payment_gateway` or a currency the gateway does not take; 502 `payment_failed`. Verified customers only.",
        "operationId": "customerPayCharge",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Charge id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "returnUrl": {
                    "type": "string",
                    "format": "uri"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Payment started",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "charge": {
                      "$ref": "#/components/schemas/CustomerCharge"
                    },
                    "checkoutUrl": {
                      "type": "string",
                      "format": "uri",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "description": "`not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "409": {
            "description": "Not payable online (`code`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "502": {
            "description": "`payment_failed`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/participants": {
      "get": {
        "tags": [
          "Customer - Participants"
        ],
        "summary": "My participants",
        "description": "The people this customer books for, by first name. With `sessionId` or `seriesId` each says whether it already holds a seat there (`alreadyBooked`, #685); an unknown session is 404 `not_found`. Verified customers only.",
        "operationId": "customerListParticipants",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "sessionId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "seriesId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Participants",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerParticipant"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "description": "Session or series not found (`not_found`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Customer - Participants"
        ],
        "summary": "Add a participant",
        "description": "Adds a person this customer books for, with the organisation's participant custom fields. 422 with `errors` per field (custom fields as `customFields.{name}`). Verified customers only.",
        "operationId": "customerCreateParticipant",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerParticipantInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "participant": {
                      "$ref": "#/components/schemas/CustomerParticipant"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "422": {
            "description": "Invalid fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerParticipantValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/participants/{id}": {
      "delete": {
        "tags": [
          "Customer - Participants"
        ],
        "summary": "Remove a participant",
        "description": "Refused with 409 `participant_has_upcoming_bookings` while it has bookings to come. Otherwise it leaves the account: unlinked when another customer shares it, deleted when never used, else anonymised so past bookings and payments keep it (#699). Verified customers only.",
        "operationId": "customerRemoveParticipant",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Removed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Has upcoming bookings (`participant_has_upcoming_bookings`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      },
      "patch": {
        "tags": [
          "Customer - Participants"
        ],
        "summary": "Change a participant",
        "description": "Fields left out keep their value; staff-only data (identifiers, notes) is never touched. 404 when the participant is not managed by this customer, 422 with `errors` per field. Verified customers only.",
        "operationId": "customerUpdateParticipant",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerParticipantInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "participant": {
                      "$ref": "#/components/schemas/CustomerParticipant"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerParticipantValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/plan-purchases/quote": {
      "post": {
        "tags": [
          "Customer - Plans and credit"
        ],
        "summary": "Price of a plan before buying",
        "description": "What buying a pass or subscription (#377) would cost with a discount code (one that applies to everything), gift cards and optionally account credit (#752). 404 `not_found`; 422 with `code`: `invalid_discount_code`, `discount_not_applicable`, `discount_minimum_not_met`, `discount_currency_mismatch`, `invalid_gift_card`. Verified customers only.",
        "operationId": "customerQuotePlanPurchase",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "planId"
                ],
                "properties": {
                  "planId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "discountCode": {
                    "type": "string"
                  },
                  "giftCardCodes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "useAccountCredit": {
                    "type": "boolean",
                    "default": false
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The quote",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerPlanPurchaseQuote"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "description": "`not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "Code or gift card refused (`code`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/plan-purchases": {
      "post": {
        "tags": [
          "Customer - Plans and credit"
        ],
        "summary": "Buy a pass or subscription",
        "description": "Buys a plan (#377). Paid online: send the browser to `checkoutUrl` (null when nothing is left to pay); the plan is granted once the payment is paid. With `paymentMethod` on_site or bank_transfer (#607): reserved, with `offlinePayment` saying how to pay, granted once staff record the payment. Discount codes and gift cards are taken up front and given back if it stays unpaid. A pending purchase holds its place within the maximum sold for an hour. 404 `not_found`; 409 with `code` when the plan cannot be bought now (not on sale, sold out, `no_payment_gateway`, `payment_method_unavailable`, a currency the gateway does not take); 422 for the input or a code/gift card (`code`); 502 `payment_failed`. Verified customers only.",
        "operationId": "customerPurchasePlan",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerPlanPurchaseInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Bought or reserved",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "purchase": {
                      "$ref": "#/components/schemas/CustomerPlanPurchase"
                    },
                    "checkoutUrl": {
                      "type": "string",
                      "format": "uri",
                      "nullable": true
                    },
                    "offlinePayment": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CustomerPlanPurchaseOfflinePayment"
                        }
                      ],
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "description": "`not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "409": {
            "description": "Not available now (`code`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "Invalid (`code`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "502": {
            "description": "`payment_failed`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/plan-purchases/{id}": {
      "get": {
        "tags": [
          "Customer - Plans and credit"
        ],
        "summary": "One of my plan purchases",
        "description": "A purchase of the customer; an online one still pending is checked with the payment provider first (the return page after paying). 404 for someone else's. Verified customers only.",
        "operationId": "customerGetPlanPurchase",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Plan purchase id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The purchase",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "purchase": {
                      "$ref": "#/components/schemas/CustomerPlanPurchase"
                    },
                    "offlinePayment": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CustomerPlanPurchaseOfflinePayment"
                        }
                      ],
                      "nullable": true,
                      "description": "How to pay a plan bought to pay later"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/plan-purchases/{id}/receipt.pdf": {
      "get": {
        "tags": [
          "Customer - Plans and credit"
        ],
        "summary": "Receipt of a plan purchase (PDF)",
        "description": "A payment confirmation of a paid plan purchase (#891; not an invoice) in the customer's language. 404 for someone else's purchase or one that is not paid. Verified customers only.",
        "operationId": "customerPlanPurchaseReceipt",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Plan purchase id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The receipt",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/personal-data": {
      "get": {
        "tags": [
          "Customer - Privacy"
        ],
        "summary": "Download my personal data",
        "description": "Everything the organisation keeps about this customer and their participants as a JSON download (`my-data-{date}.json`, GDPR #400). Audit-logged (`customer.data_exported`). Verified customers only.",
        "operationId": "customerExportPersonalData",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The export (attachment)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/deletion-request/code": {
      "post": {
        "tags": [
          "Customer - Privacy"
        ],
        "summary": "Email me a code to delete my account",
        "description": "Sends a code (purpose `account_deletion`) to the customer's address, to confirm with POST /deletion-request. At most 3 an hour (429). Verified customers only.",
        "operationId": "customerRequestDeletionCode",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "locale": {
                    "description": "Language of the email; default the customer's language",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Code sent",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "email": {
                      "type": "string",
                      "format": "email"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "429": {
            "description": "Too many attempts (`retryAfter` seconds)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/deletion-request": {
      "post": {
        "tags": [
          "Customer - Privacy"
        ],
        "summary": "Delete my account",
        "description": "Confirms with the emailed code (400 `invalid_code` when wrong or expired; a wrong guess counts as an attempt). Without upcoming bookings the account is anonymised now (200, `deleted`); otherwise it is scheduled for when they have passed (202, `scheduled`, `upcomingBookings` true). Verified customers only.",
        "operationId": "customerRequestDeletion",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "example": "123456"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "deleted",
                        "scheduled"
                      ]
                    },
                    "upcomingBookings": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "202": {
            "description": "Scheduled until the upcoming bookings have passed",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "deleted",
                        "scheduled"
                      ]
                    },
                    "upcomingBookings": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid or expired code (`invalid_code`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/push": {
      "get": {
        "tags": [
          "Customer - Calendar and push"
        ],
        "summary": "My push notification devices",
        "description": "Whether this Plek installation offers web push (#356), its VAPID public key for `PushManager.subscribe`, and the customer's devices that receive notifications. Verified customers only.",
        "operationId": "customerGetPush",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Push settings",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "available": {
                      "type": "boolean"
                    },
                    "publicKey": {
                      "description": "VAPID application server key",
                      "type": "string",
                      "nullable": true
                    },
                    "devices": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerPushDevice"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/push/subscriptions": {
      "post": {
        "tags": [
          "Customer - Calendar and push"
        ],
        "summary": "Turn on push notifications on this device",
        "description": "Stores the browser's push subscription (the JSON of `PushSubscription`) for the customer (#356); the device label comes from the User-Agent. 409 `push_unavailable` without VAPID keys, 400 `invalid_subscription`. Verified customers only.",
        "operationId": "customerSubscribePush",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "endpoint",
                  "keys"
                ],
                "properties": {
                  "endpoint": {
                    "type": "string",
                    "format": "uri"
                  },
                  "keys": {
                    "properties": {
                      "p256dh": {
                        "type": "string"
                      },
                      "auth": {
                        "type": "string"
                      }
                    },
                    "type": "object"
                  },
                  "contentEncoding": {
                    "type": "string",
                    "default": "aes128gcm"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Subscribed",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "device": {
                      "$ref": "#/components/schemas/CustomerPushDevice"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_subscription`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "409": {
            "description": "`push_unavailable`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/push/subscriptions/{id}": {
      "delete": {
        "tags": [
          "Customer - Calendar and push"
        ],
        "summary": "Stop push notifications on a device",
        "description": "Removes one of the customer's devices. 404 for an unknown device. Verified customers only.",
        "operationId": "customerUnsubscribePush",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Device id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Removed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/public/{org_slug}/push/unsubscribe": {
      "post": {
        "tags": [
          "Public - Preferences"
        ],
        "summary": "A device stops push notifications",
        "description": "Called by the customer app when signing out (#356): removes the push subscription with this endpoint, without authentication. 204 whether or not it existed; 404 without an endpoint or for an unknown organisation.",
        "operationId": "publicUnsubscribePush",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "endpoint"
                ],
                "properties": {
                  "endpoint": {
                    "type": "string",
                    "format": "uri"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Removed (or never there)"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/customer/{org_slug}/reviews": {
      "get": {
        "tags": [
          "Customer - Reviews"
        ],
        "summary": "My reviews and what to review",
        "description": "When the organisation collects reviews (#355): the activities the customer attended to review (`prompts`, with this term's review if written; one review per activity per term of `termWeeks`) and the reviews they wrote. `enabled` false with empty lists otherwise. Verified customers only.",
        "operationId": "customerListReviews",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Reviews",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "enabled": {
                      "type": "boolean"
                    },
                    "termWeeks": {
                      "type": "integer"
                    },
                    "prompts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerReviewPrompt"
                      }
                    },
                    "reviews": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerReview"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Customer - Reviews"
        ],
        "summary": "Review an activity",
        "description": "Writes (or within the same term changes) the customer's review of an activity they attended (#355), optionally about one of its coaches. After 4 or 5 stars `googleReviewUrl` is the organisation's Google review page (#753), else null. 400 without an activity or a whole rating 1-5; 422 `not_reviewable` when the customer did not attend it or reviews are off. Verified customers only.",
        "operationId": "customerSubmitReview",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "activityId",
                  "rating"
                ],
                "properties": {
                  "activityId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "rating": {
                    "type": "integer",
                    "maximum": 5,
                    "minimum": 1
                  },
                  "comment": {
                    "type": "string",
                    "nullable": true
                  },
                  "coachId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Saved",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "review": {
                      "$ref": "#/components/schemas/CustomerReview"
                    },
                    "googleReviewUrl": {
                      "type": "string",
                      "format": "uri",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "422": {
            "description": "`not_reviewable`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/customer-tags": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Customer tags",
        "description": "The organisation's managed list of customer tags (#758), with how many customers carry each. Staff only. Needs customer:read.",
        "operationId": "listCustomerTags",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Tags",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "tags": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffCustomerTag"
                      }
                    },
                    "limit": {
                      "description": "At most this many tags",
                      "type": "integer",
                      "example": 100
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Add a customer tag",
        "description": "Names are unique case-insensitively (409 code duplicate_name), at most 40 characters; 422 codes invalid_name, invalid_colour, limit_reached. Audit-logged. Needs customer:write.",
        "operationId": "createCustomerTag",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 40
                  },
                  "colour": {
                    "type": "string",
                    "enum": [
                      "grey",
                      "red",
                      "orange",
                      "yellow",
                      "green",
                      "teal",
                      "blue",
                      "purple",
                      "pink"
                    ]
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "tag": {
                      "$ref": "#/components/schemas/StaffCustomerTag"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "409": {
            "description": "Name taken (code duplicate_name)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "invalid_name, invalid_colour or limit_reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/customer-tags/{id}": {
      "put": {
        "tags": [
          "Customers"
        ],
        "summary": "Rename or recolour a customer tag",
        "description": "Fields left out stay as they are. Audit-logged. Needs customer:write.",
        "operationId": "updateCustomerTag",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Customer tag id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 40
                  },
                  "colour": {
                    "type": "string",
                    "enum": [
                      "grey",
                      "red",
                      "orange",
                      "yellow",
                      "green",
                      "teal",
                      "blue",
                      "purple",
                      "pink"
                    ]
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "tag": {
                      "$ref": "#/components/schemas/StaffCustomerTag"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "409": {
            "description": "Name taken (code duplicate_name)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "invalid_name or invalid_colour",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Customers"
        ],
        "summary": "Delete a customer tag",
        "description": "It comes off every customer. Audit-logged. Needs customer:write.",
        "operationId": "deleteCustomerTag",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Customer tag id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "404": {
            "description": "Not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/customers/{id}/tags": {
      "put": {
        "tags": [
          "Customers"
        ],
        "summary": "Set the tags of a customer",
        "description": "Replaces the customer tags (#758); audit-logged (customer.tags_changed). Needs customer:write.",
        "operationId": "setCustomerTags",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Customer id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "tagIds"
                ],
                "properties": {
                  "tagIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tags now on the customer",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "tags": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffCustomerTagRef"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "tagIds is not a list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Unknown tag (code unknown_tag)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/customers/tags": {
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Tag or untag a selection of customers",
        "description": "Puts one tag on, or takes it off, the selected customers (#758). Needs customer:write.",
        "operationId": "bulkTagCustomers",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "action",
                  "tagId",
                  "customerIds"
                ],
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "add",
                      "remove"
                    ]
                  },
                  "tagId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "customerIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Customers changed",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "changed": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid action, tag id or customer ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Unknown tag (code unknown_tag)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/waitlist": {
      "get": {
        "tags": [
          "Customer - Waitlist and interest"
        ],
        "summary": "My waiting lists",
        "description": "The waiting lists the customer's participants and parties are on (active entries: waiting, or offered a spot until `expiresAt`), including seats kept for next term (re-enrolment, #736), soonest first. Verified customers only.",
        "operationId": "customerListWaitlist",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Entries",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "entries": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerWaitlistEntry"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/customer/{org_slug}/waitlist/{id}": {
      "delete": {
        "tags": [
          "Customer - Waitlist and interest"
        ],
        "summary": "Leave a waiting list",
        "description": "Takes the participant or party off the waiting list; a spot offered to them passes on to the next in line. For a re-enrolment offer (#736) this declines the seat kept for next term. 409 `not_active` when no longer on the list. Verified customers only.",
        "operationId": "customerLeaveWaitlist",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Waiting list entry id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Left",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "entry": {
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "status": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CustomerEmailNotVerified"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`not_active`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "customerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/discount-codes": {
      "get": {
        "tags": [
          "Discount codes"
        ],
        "summary": "Discount codes",
        "description": "Paginated. Needs discount_code:read.",
        "operationId": "listDiscountCodes",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "active",
            "in": "query",
            "description": "Only active codes",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page, from 1",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Items per page",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Discount codes",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "discountCodes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffDiscountCode"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/StaffPageMetadata"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Discount codes"
        ],
        "summary": "Create a discount code",
        "description": "Typed or automatic (#744: applied to every customer checkout during its validity; at most one code per checkout, the one that takes off most). Required: code, description, type, applicability, plus percentageValue or fixedAmountValue + fixedAmountCurrency. 400 for invalid input or a code that exists. Needs discount_code:write.",
        "operationId": "createDiscountCode",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffDiscountCodeInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "discountCode": {
                      "$ref": "#/components/schemas/StaffDiscountCode"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or duplicate code",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/discount-codes/{id}": {
      "get": {
        "tags": [
          "Discount codes"
        ],
        "summary": "One discount code",
        "description": "Needs discount_code:read.",
        "operationId": "getDiscountCode",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Discount code id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The code",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "discountCode": {
                      "$ref": "#/components/schemas/StaffDiscountCode"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Discount codes"
        ],
        "summary": "Change a discount code",
        "description": "Description, applicability, minimum order, validity, limits, active, automatic, item scope and conditions; code, type and value stay. Needs discount_code:write.",
        "operationId": "updateDiscountCode",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Discount code id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffDiscountCodeInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "discountCode": {
                      "$ref": "#/components/schemas/StaffDiscountCode"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Discount codes"
        ],
        "summary": "Delete a discount code",
        "description": "Only a code never used; deactivate a used one instead (400). Needs discount_code:write.",
        "operationId": "deleteDiscountCode",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Discount code id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Used before",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/discount-codes/validate": {
      "post": {
        "tags": [
          "Discount codes"
        ],
        "summary": "Check a discount code for an order",
        "description": "Whether a code applies to a session or series booking of this amount (for a staff booking) and what it takes off; an invalid code answers 200 with valid false and the reason. Needs booking:write.",
        "operationId": "validateStaffDiscountCode",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "code",
                  "bookingType",
                  "orderAmountValue",
                  "orderAmountCurrency"
                ],
                "properties": {
                  "code": {
                    "type": "string"
                  },
                  "bookingType": {
                    "type": "string",
                    "enum": [
                      "session",
                      "series"
                    ]
                  },
                  "orderAmountValue": {
                    "description": "Cents",
                    "type": "integer"
                  },
                  "orderAmountCurrency": {
                    "type": "string"
                  },
                  "customerId": {
                    "description": "For the per-customer limit",
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "valid": {
                      "type": "boolean"
                    },
                    "error": {
                      "description": "Why it does not apply (valid false)",
                      "type": "string"
                    },
                    "discountCode": {
                      "$ref": "#/components/schemas/StaffDiscountCode"
                    },
                    "discountAmount": {
                      "$ref": "#/components/schemas/StaffValueAmount"
                    },
                    "discountedPrice": {
                      "$ref": "#/components/schemas/StaffValueAmount"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "A field is missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/test-smtp": {
      "post": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Test SMTP settings",
        "description": "Sends a test email to `email` through the SMTP server given (nothing is saved). A password left out falls back to the stored one; the sender is the organisation's sender address, else the SMTP username. Needs email_settings:write.",
        "operationId": "testSmtpEmail",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "email",
                  "host",
                  "username"
                ],
                "properties": {
                  "email": {
                    "description": "Where the test email goes",
                    "type": "string",
                    "format": "email"
                  },
                  "host": {
                    "type": "string"
                  },
                  "port": {
                    "type": "integer",
                    "default": 587
                  },
                  "username": {
                    "type": "string"
                  },
                  "password": {
                    "description": "Left out: the stored password",
                    "type": "string"
                  },
                  "encryption": {
                    "type": "string",
                    "default": "tls",
                    "enum": [
                      "tls",
                      "ssl"
                    ]
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailTestResult"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid settings, or the provider refused the email (the error says why)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/test-gmail": {
      "post": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Test the Gmail connection",
        "description": "Sends a test email to `email` from the connected Gmail account (400 when Gmail is not connected). Needs email_settings:write.",
        "operationId": "testGmailEmail",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailTestResult"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid settings, or the provider refused the email (the error says why)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/test-brevo": {
      "post": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Test a Brevo API key",
        "description": "Sends a test email to `email` through the organisation's own Brevo account. An API key left out falls back to the stored one; needs a sender address in the email settings. Needs email_settings:write.",
        "operationId": "testBrevoEmail",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "api_key": {
                    "description": "Left out: the stored key",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailTestResult"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid settings, or the provider refused the email (the error says why)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/test-default-email": {
      "post": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Test Plek's own email service",
        "description": "Sends a test email to `email` through the platform's email service, with the organisation's sender name and address (400 without a sender address). Needs email_settings:write.",
        "operationId": "testDefaultEmail",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailTestResult"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid settings, or the provider refused the email (the error says why)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/email-templates": {
      "get": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Customer emails and their wording",
        "description": "Every editable customer email (#399) with its placeholders and which languages are customised. Staff and platform emails (sign-in codes, invitations) are not editable. Needs email_settings:read.",
        "operationId": "listEmailTemplates",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The emails",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailTemplateList"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/email-templates/{key}/{language}": {
      "get": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "One email in one language",
        "description": "The wording in use (the organisation's, else the default for its wording preset) and the default. Needs email_settings:read.",
        "operationId": "getEmailTemplate",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "key",
            "in": "path",
            "description": "The email, e.g. booking_confirmed (see the list)",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-z_]+$"
            }
          },
          {
            "name": "language",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "en",
                "nl",
                "fr",
                "de"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The email",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailTemplate"
                }
              }
            }
          },
          "404": {
            "description": "Unknown email template or unsupported language",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Change an email's wording",
        "description": "Only the placeholders of this email may be used; block placeholders only in the body. Audit-logged. Needs email_settings:write.",
        "operationId": "saveEmailTemplate",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "key",
            "in": "path",
            "description": "The email, e.g. booking_confirmed (see the list)",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-z_]+$"
            }
          },
          {
            "name": "language",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "en",
                "nl",
                "fr",
                "de"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "subject",
                  "body"
                ],
                "properties": {
                  "subject": {
                    "type": "string"
                  },
                  "body": {
                    "description": "Text with {{ placeholders }}",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailTemplate"
                }
              }
            }
          },
          "404": {
            "description": "Unknown email template or unsupported language",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Not valid: `problems` per field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailTemplateProblems"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Back to the default wording",
        "description": "Needs email_settings:write.",
        "operationId": "resetEmailTemplate",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "key",
            "in": "path",
            "description": "The email, e.g. booking_confirmed (see the list)",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-z_]+$"
            }
          },
          {
            "name": "language",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "en",
                "nl",
                "fr",
                "de"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The email with its default wording",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailTemplate"
                }
              }
            }
          },
          "404": {
            "description": "Unknown email template or unsupported language",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/email-templates/{key}/{language}/preview": {
      "post": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Preview an email",
        "description": "Renders the draft (or the wording in use for what is left out) with example data and the organisation's branding. Nothing is saved or sent. Needs email_settings:read.",
        "operationId": "previewEmailTemplate",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "key",
            "in": "path",
            "description": "The email, e.g. booking_confirmed (see the list)",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-z_]+$"
            }
          },
          {
            "name": "language",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "en",
                "nl",
                "fr",
                "de"
              ]
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailTemplateDraft"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The rendered email",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "subject": {
                      "type": "string"
                    },
                    "html": {
                      "type": "string"
                    },
                    "text": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Unknown email template or unsupported language",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Not valid: `problems` per field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailTemplateProblems"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/email-templates/{key}/{language}/test": {
      "post": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Send a test of an email",
        "description": "Sends the draft (or the wording in use) with example data to the signed-in staff member (an API key: its user) through the organisation's email settings. Needs email_settings:write.",
        "operationId": "sendTestEmailTemplate",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "key",
            "in": "path",
            "description": "The email, e.g. booking_confirmed (see the list)",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-z_]+$"
            }
          },
          {
            "name": "language",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "en",
                "nl",
                "fr",
                "de"
              ]
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailTemplateDraft"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "sentTo": {
                      "type": "string",
                      "format": "email"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Unknown email template or unsupported language",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Not valid: `problems` per field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailTemplateProblems"
                }
              }
            }
          },
          "502": {
            "description": "The email provider refused it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/entitlements": {
      "get": {
        "tags": [
          "Plans and entitlements"
        ],
        "summary": "Holdings",
        "description": "With customerId: that customer's holdings (series perks synced first, each with its online purchase) plus awaitingPayment, plans bought to pay on site or by bank transfer (#607). Without: the organisation's latest holdings. 404 for an unknown customer. Needs entitlement:read.",
        "operationId": "listEntitlements",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "customerId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "sourceType",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "absence",
                "subscription",
                "pass",
                "series_perk",
                "manual"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Holdings",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "entitlements": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffEntitlement"
                      }
                    },
                    "awaitingPayment": {
                      "description": "Only with customerId",
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffPlanPurchase"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Plans and entitlements"
        ],
        "summary": "Grant a plan or a credit",
        "description": "Either a plan sold at the desk ({customerId, planId, participantId (subscriptions), startDate?, paidAmount?, note?}) or a manual credit ({customerId, participantId?, name, services, credits?, validUntil?, reason}). Audit-logged. 400 for invalid input (unknown customer or plan, a subscription without one of the customer's participants), 409 with code when the plan cannot be granted now. Needs entitlement:write.",
        "operationId": "grantEntitlement",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "customerId"
                ],
                "properties": {
                  "customerId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "planId": {
                    "description": "Grant this plan; leave out for a manual credit",
                    "type": "string",
                    "format": "uuid"
                  },
                  "participantId": {
                    "description": "Required for a subscription",
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  },
                  "startDate": {
                    "type": "string",
                    "format": "date",
                    "nullable": true
                  },
                  "paidAmount": {
                    "description": "Cents paid at the desk",
                    "type": "integer",
                    "nullable": true
                  },
                  "note": {
                    "type": "string",
                    "nullable": true
                  },
                  "name": {
                    "description": "Manual credit: its name",
                    "type": "string"
                  },
                  "services": {
                    "description": "Manual credit: what it books",
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/StaffServiceRule"
                    }
                  },
                  "credits": {
                    "type": "integer",
                    "nullable": true
                  },
                  "validUntil": {
                    "type": "string",
                    "format": "date",
                    "nullable": true
                  },
                  "reason": {
                    "description": "Manual credit: why",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Granted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffEntitlement"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "The plan cannot be granted now",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/entitlements/report": {
      "get": {
        "tags": [
          "Plans and entitlements"
        ],
        "summary": "Plans report",
        "description": "Per plan: sold, active, revenue (cents, as paid when granted) and credits outstanding; totals; bookings made with holdings per activity over the last 12 months. Needs plan:read.",
        "operationId": "getEntitlementReport",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Report",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "plans": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "planId": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "kind": {
                            "type": "string",
                            "enum": [
                              "subscription",
                              "pass"
                            ]
                          },
                          "sold": {
                            "type": "integer"
                          },
                          "active": {
                            "type": "integer"
                          },
                          "revenue": {
                            "description": "Cents in the plan currency",
                            "type": "integer"
                          },
                          "creditsOutstanding": {
                            "type": "integer"
                          }
                        },
                        "type": "object"
                      }
                    },
                    "totals": {
                      "properties": {
                        "sold": {
                          "type": "integer"
                        },
                        "active": {
                          "type": "integer"
                        },
                        "creditsOutstanding": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "usageByActivity": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "activityId": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "activityName": {
                            "type": "string",
                            "nullable": true
                          },
                          "uses": {
                            "type": "integer"
                          }
                        },
                        "type": "object"
                      }
                    },
                    "since": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/entitlements/{id}": {
      "get": {
        "tags": [
          "Plans and entitlements"
        ],
        "summary": "One holding",
        "description": "With its usage ledger, newest first, and its online purchase. Needs entitlement:read.",
        "operationId": "getEntitlement",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Entitlement id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The holding",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/StaffEntitlement"
                    },
                    {
                      "properties": {
                        "ledger": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/StaffEntitlementUsage"
                          }
                        }
                      },
                      "type": "object"
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/entitlements/{id}/adjust": {
      "post": {
        "tags": [
          "Plans and entitlements"
        ],
        "summary": "Add or remove credits",
        "description": "credits +n adds, -n removes, with a reason; recorded in the ledger. Audit-logged. Needs entitlement:write.",
        "operationId": "adjustEntitlementCredits",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Entitlement id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "credits",
                  "reason"
                ],
                "properties": {
                  "credits": {
                    "type": "integer",
                    "example": 2
                  },
                  "reason": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Changed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffEntitlement"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/entitlements/{id}/extend": {
      "post": {
        "tags": [
          "Plans and entitlements"
        ],
        "summary": "Change the end date",
        "description": "A new validUntil (Y-m-d), with a reason. Audit-logged. Needs entitlement:write.",
        "operationId": "extendEntitlement",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Entitlement id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "validUntil",
                  "reason"
                ],
                "properties": {
                  "validUntil": {
                    "type": "string",
                    "format": "date"
                  },
                  "reason": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Changed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffEntitlement"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/entitlements/{id}/cancel": {
      "post": {
        "tags": [
          "Plans and entitlements"
        ],
        "summary": "Cancel a holding",
        "description": "With a reason; optionally refunds a plan bought online (#536) through the gateway: all that is left (refundAmount null) or refundAmount cents. Refunding needs payment:refund (403 otherwise). Audit-logged. Needs entitlement:write.",
        "operationId": "cancelEntitlement",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Entitlement id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "reason"
                ],
                "properties": {
                  "reason": {
                    "type": "string"
                  },
                  "refund": {
                    "type": "boolean",
                    "default": false
                  },
                  "refundAmount": {
                    "description": "Cents; null: all that is refundable",
                    "type": "integer",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffEntitlement"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/entitlements/plan-purchases/{id}/payment": {
      "post": {
        "tags": [
          "Plans and entitlements"
        ],
        "summary": "Record the payment of a plan bought to pay later",
        "description": "A plan bought to pay on site or by bank transfer (#607) was paid: grants it. 404 unknown purchase, 400 unknown method or not awaiting payment. Needs payment:write.",
        "operationId": "recordPlanPurchasePayment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Plan purchase id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "method"
                ],
                "properties": {
                  "method": {
                    "type": "string",
                    "enum": [
                      "cash",
                      "cardterminal",
                      "banktransfer",
                      "other"
                    ]
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Paid and granted",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "purchase": {
                      "$ref": "#/components/schemas/CustomerPlanPurchase"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/entitlements/plan-purchases/{id}/cancel": {
      "post": {
        "tags": [
          "Plans and entitlements"
        ],
        "summary": "Cancel an unpaid plan purchase",
        "description": "A plan bought to pay later that is still unpaid; gift cards taken are given back. 404 unknown purchase, 400 when it cannot be cancelled. Needs entitlement:write.",
        "operationId": "cancelPlanPurchase",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Plan purchase id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "purchase": {
                      "$ref": "#/components/schemas/CustomerPlanPurchase"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Cannot be cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/equipment": {
      "get": {
        "tags": [
          "Locations"
        ],
        "summary": "Equipment",
        "description": "Page by page (page, per_page). Needs equipment:read.",
        "operationId": "listEquipment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Equipment",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "equipment": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffEquipment"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/StaffPageMetadata"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Locations"
        ],
        "summary": "Add equipment",
        "description": "Needs equipment:write.",
        "operationId": "createEquipment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffEquipmentInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "equipment": {
                      "$ref": "#/components/schemas/StaffEquipment"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Name missing or invalid value",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/equipment/{id}": {
      "get": {
        "tags": [
          "Locations"
        ],
        "summary": "One piece of equipment",
        "description": "Needs equipment:read.",
        "operationId": "getEquipment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Equipment id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The equipment",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "equipment": {
                      "$ref": "#/components/schemas/StaffEquipment"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Locations"
        ],
        "summary": "Change equipment",
        "description": "The whole item again. Needs equipment:write.",
        "operationId": "updateEquipment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Equipment id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffEquipmentInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "equipment": {
                      "$ref": "#/components/schemas/StaffEquipment"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Name missing or invalid value",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Locations"
        ],
        "summary": "Remove equipment",
        "description": "Needs equipment:write.",
        "operationId": "deleteEquipment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Equipment id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/exports/bookings": {
      "get": {
        "tags": [
          "Imports and exports"
        ],
        "summary": "Export bookings",
        "description": "One row per booking, filtered on the session date. With the assigned scope only what is linked to you is exported; columns follow your permissions (no payment data without payment:read). Audit-logged. Needs booking:read.",
        "operationId": "exportBookings",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ExportFormat"
          },
          {
            "name": "from",
            "in": "query",
            "description": "First day (YYYY-MM-DD, the organisation's days)",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Last day, inclusive",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "activityId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "seriesId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "sessionId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "A booking status, or active (pending and confirmed)",
            "required": false,
            "schema": {
              "type": "string",
              "example": "confirmed"
            }
          },
          {
            "name": "paymentStatus",
            "in": "query",
            "description": "A payment status, or none",
            "required": false,
            "schema": {
              "type": "string",
              "example": "paid"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ExportFile"
          },
          "400": {
            "description": "Invalid format, date or id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/exports/participants": {
      "get": {
        "tags": [
          "Imports and exports"
        ],
        "summary": "Export participants",
        "description": "One row per participant with their custom fields. With the assigned scope only what is linked to you is exported; columns follow your permissions (no payment data without payment:read). Audit-logged. Needs participant:read.",
        "operationId": "exportParticipants",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ExportFormat"
          },
          {
            "name": "activityId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "seriesId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "sessionId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ExportFile"
          },
          "400": {
            "description": "Invalid format, date or id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/exports/customers": {
      "get": {
        "tags": [
          "Imports and exports"
        ],
        "summary": "Export customers",
        "description": "One row per customer; from/to filter on when the account was created. With the assigned scope only what is linked to you is exported; columns follow your permissions (no payment data without payment:read). Audit-logged. Needs customer:read.",
        "operationId": "exportCustomers",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ExportFormat"
          },
          {
            "name": "from",
            "in": "query",
            "description": "First day (YYYY-MM-DD, the organisation's days)",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Last day, inclusive",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ExportFile"
          },
          "400": {
            "description": "Invalid format, date or id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/exports/customer-list": {
      "get": {
        "tags": [
          "Imports and exports"
        ],
        "summary": "Export the customer list as filtered",
        "description": "The customer list with the same filters and sorting as GET /customers (#727), or the customers in `ids`; at most 10000 rows (422 code too_many_rows: narrow the filters). Audit-logged. Needs customer:read.",
        "operationId": "exportCustomerList",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ExportFormat"
          },
          {
            "name": "search",
            "in": "query",
            "description": "Name, email or phone",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "verified",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "yes",
                "no"
              ]
            }
          },
          {
            "name": "plan",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "none"
              ]
            }
          },
          {
            "name": "planExpiresWithin",
            "in": "query",
            "description": "Days",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "noBookingFor",
            "in": "query",
            "description": "Days: no booked session in the last N days and none ahead",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "fees",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "none"
              ]
            }
          },
          {
            "name": "outstanding",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "yes",
                "no"
              ]
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "name",
                "email",
                "createdAt",
                "lastBooking",
                "nextBooking",
                "planExpiry"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "ids",
            "in": "query",
            "description": "Comma-separated customer ids",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tags",
            "in": "query",
            "description": "Comma-separated tag ids: customers carrying at least one",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ExportFile"
          },
          "400": {
            "description": "Invalid format or filter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "More than 10000 customers (code too_many_rows)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/exports/payments": {
      "get": {
        "tags": [
          "Imports and exports"
        ],
        "summary": "Export payments",
        "description": "One row per payment and refund in the period. With the assigned scope only what is linked to you is exported; columns follow your permissions (no payment data without payment:read). Audit-logged. Needs payment:read.",
        "operationId": "exportPayments",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ExportFormat"
          },
          {
            "name": "from",
            "in": "query",
            "description": "First day (YYYY-MM-DD, the organisation's days)",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Last day, inclusive",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ExportFile"
          },
          "400": {
            "description": "Invalid format, date or id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/exports/sales-journal": {
      "get": {
        "tags": [
          "Imports and exports"
        ],
        "summary": "Export the sales journal",
        "description": "For the accountant (#737): one row per sale line (#891) paid or refunded in the period, in the organisation's days, with VAT rate, net, VAT and gross, product code, payment method and a stable line_id. Without a period: everything until today. Audit-logged. Needs payment:read.",
        "operationId": "exportSalesJournal",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ExportFormat"
          },
          {
            "name": "from",
            "in": "query",
            "description": "First day (YYYY-MM-DD, the organisation's days)",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Last day, inclusive",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ExportFile"
          },
          "400": {
            "description": "Invalid format, date or id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/exports/attendance": {
      "get": {
        "tags": [
          "Imports and exports"
        ],
        "summary": "Export attendance",
        "description": "One row per attendee and session with check-in and absence. With the assigned scope only what is linked to you is exported; columns follow your permissions (no payment data without payment:read). Audit-logged. Needs attendance:read.",
        "operationId": "exportAttendance",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ExportFormat"
          },
          {
            "name": "from",
            "in": "query",
            "description": "First day (YYYY-MM-DD, the organisation's days)",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Last day, inclusive",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "activityId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "seriesId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "sessionId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ExportFile"
          },
          "400": {
            "description": "Invalid format, date or id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/exports/incidents": {
      "get": {
        "tags": [
          "Imports and exports"
        ],
        "summary": "Export incident reports",
        "description": "The incident log (#751) of sessions in the period. With the assigned scope only what is linked to you is exported; columns follow your permissions (no payment data without payment:read). Audit-logged. Needs attendance:read.",
        "operationId": "exportIncidents",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ExportFormat"
          },
          {
            "name": "from",
            "in": "query",
            "description": "First day (YYYY-MM-DD, the organisation's days)",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Last day, inclusive",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "activityId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "seriesId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "sessionId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ExportFile"
          },
          "400": {
            "description": "Invalid format, date or id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/extras": {
      "get": {
        "tags": [
          "Activities"
        ],
        "summary": "Extras",
        "description": "The extras catalogue (#793), in sort order. Needs activity:read.",
        "operationId": "listExtras",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "archived",
            "in": "query",
            "description": "1 includes archived extras",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Extras",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "extras": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffExtra"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Activities"
        ],
        "summary": "Add an extra",
        "description": "Priced per unit in one currency (never converted); offered by the activities that pick it. Needs activity:write.",
        "operationId": "createExtra",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffExtraInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "extra": {
                      "$ref": "#/components/schemas/StaffExtra"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "422": {
            "description": "Invalid (code validation_failed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/extras/{id}": {
      "get": {
        "tags": [
          "Activities"
        ],
        "summary": "One extra",
        "description": "Needs activity:read.",
        "operationId": "getExtra",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Extra id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The extra",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "extra": {
                      "$ref": "#/components/schemas/StaffExtra"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Extra not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Activities"
        ],
        "summary": "Change an extra",
        "description": "Replaces the extra (fields left out take their default; translations left out stay). Bookings made keep their snapshot of name and price. Needs activity:write.",
        "operationId": "updateExtra",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Extra id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffExtraInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "extra": {
                      "$ref": "#/components/schemas/StaffExtra"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Extra not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "Invalid (code validation_failed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Activities"
        ],
        "summary": "Remove an extra",
        "description": "Deleted when never booked, else archived (no longer offered). Needs activity:write.",
        "operationId": "deleteExtra",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Extra id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted or archived",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "archived": {
                      "type": "boolean"
                    },
                    "deleted": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Extra not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/extras/order": {
      "put": {
        "tags": [
          "Activities"
        ],
        "summary": "Order the extras",
        "description": "The extras in the order given come first, in that order. Needs activity:write.",
        "operationId": "reorderExtras",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "extraIds"
                ],
                "properties": {
                  "extraIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The extras in their new order",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "extras": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffExtra"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "422": {
            "description": "Invalid (code validation_failed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/extras/{id}/image": {
      "post": {
        "tags": [
          "Activities"
        ],
        "summary": "Set the picture of an extra",
        "description": "Replaces the picture. Needs activity:write.",
        "operationId": "uploadExtraImage",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Extra id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "required": [
                  "image"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The extra with its picture",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "extra": {
                      "$ref": "#/components/schemas/StaffExtra"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Extra not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "No picture, or not an accepted image (code validation_failed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Activities"
        ],
        "summary": "Remove the picture of an extra",
        "description": "Needs activity:write.",
        "operationId": "deleteExtraImage",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Extra id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The extra without picture",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "extra": {
                      "$ref": "#/components/schemas/StaffExtra"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Extra not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/activities/{id}/extras": {
      "put": {
        "tags": [
          "Activities"
        ],
        "summary": "Choose the extras an activity offers",
        "description": "The extras its headcount checkout offers (#793); replaces the list. Needs activity:write.",
        "operationId": "setActivityExtras",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "extraIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The offered extras",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "activityId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "offeredExtraIds": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "format": "uuid"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Activity not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "Unknown extra (code validation_failed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/gift-cards": {
      "get": {
        "tags": [
          "Gift cards"
        ],
        "summary": "Gift cards",
        "description": "Gift cards and account credit, newest first by default, paginated. 400 for an unknown sort key. Needs gift_card:read.",
        "operationId": "listGiftCards",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "active",
                "depleted",
                "expired"
              ]
            }
          },
          {
            "name": "code",
            "in": "query",
            "description": "Find by code",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "createdAt",
                "code",
                "initialAmount",
                "currentBalance",
                "status",
                "expiresAt"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page, from 1",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Items per page",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Gift cards",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "giftCards": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffGiftCard"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/StaffPageMetadata"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Gift cards"
        ],
        "summary": "Create a gift card",
        "description": "A gift card made by staff (not a sale), active at once, with a note on why. Account credit for one customer is issued with POST /customers/{id}/credit instead. 400 when amount or adminNote is missing or invalid. Needs gift_card:write.",
        "operationId": "createGiftCard",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "amount",
                  "adminNote"
                ],
                "properties": {
                  "amount": {
                    "description": "Cents",
                    "type": "integer",
                    "minimum": 1
                  },
                  "currency": {
                    "description": "Default: the organisation's currency",
                    "type": "string"
                  },
                  "adminNote": {
                    "type": "string"
                  },
                  "recipientEmail": {
                    "type": "string",
                    "format": "email",
                    "nullable": true
                  },
                  "recipientName": {
                    "type": "string",
                    "nullable": true
                  },
                  "personalMessage": {
                    "type": "string",
                    "nullable": true
                  },
                  "taxCategoryId": {
                    "type": "string",
                    "nullable": true
                  },
                  "taxRate": {
                    "description": "Basis points",
                    "type": "integer",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "giftCard": {
                      "$ref": "#/components/schemas/StaffGiftCard"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/gift-cards/{id}": {
      "get": {
        "tags": [
          "Gift cards"
        ],
        "summary": "One gift card",
        "description": "With where it was used: its redemptions on bookings, newest first (#415). Needs gift_card:read.",
        "operationId": "getGiftCard",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Gift card id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The gift card",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "giftCard": {
                      "$ref": "#/components/schemas/StaffGiftCard"
                    },
                    "usage": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "bookingId": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "amount": {
                            "$ref": "#/components/schemas/StaffBookingStringAmount"
                          },
                          "redeemedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "refundedAt": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/gift-cards/{id}/pdf": {
      "get": {
        "tags": [
          "Gift cards"
        ],
        "summary": "Gift card as PDF",
        "description": "A printable gift card with its code. Needs gift_card:read.",
        "operationId": "getGiftCardPdf",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Gift card id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The PDF",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/email-integrations/gmail": {
      "get": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Gmail connection status",
        "description": "Whether a Gmail account is connected to send the organisation's email, and which provider is in use. Needs email_settings:read.",
        "operationId": "getGmailIntegration",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Status",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "configured": {
                      "type": "boolean"
                    },
                    "email_provider": {
                      "type": "string",
                      "enum": [
                        "default",
                        "brevo",
                        "smtp",
                        "gmail",
                        "sendgrid",
                        "mailgun"
                      ]
                    },
                    "token_expired": {
                      "description": "Null when not connected",
                      "type": "boolean",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Disconnect Gmail",
        "description": "Forgets the Gmail tokens; when Gmail was the provider, email goes through Plek's own service again. Needs email_settings:write.",
        "operationId": "disconnectGmailIntegration",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Disconnected",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/email-integrations/gmail/authorize": {
      "get": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Start connecting Gmail",
        "description": "Internal: the staff app's \"Connect Gmail\" button. Answers Google's consent URL; Google returns to Plek's callback, which redirects to the staff app's settings page, so the flow only works from a browser in the staff app. Needs email_settings:write.",
        "operationId": "startGmailAuthorization",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Where to send the browser",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "authorization_url": {
                      "type": "string",
                      "format": "uri"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-internal": true
      }
    },
    "/api/org/{organisation_id}/activities/{id}/headcount": {
      "get": {
        "tags": [
          "Activities"
        ],
        "summary": "How an activity is booked",
        "description": "Attendee mode, party rules, ticket types (archived ones too), offered extras and booking style (#786, #794). Needs activity:read.",
        "operationId": "getActivityHeadcount",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Headcount settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffActivityHeadcount"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/activities/{id}/attendee-mode": {
      "put": {
        "tags": [
          "Activities"
        ],
        "summary": "Book named participants or a headcount",
        "description": "Changes only while the activity has no active bookings. Needs activity:write.",
        "operationId": "setActivityAttendeeMode",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "attendeeMode": {
                    "type": "string",
                    "enum": [
                      "participants",
                      "headcount"
                    ]
                  },
                  "headcount": {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/ActivityHeadcountSettings"
                      }
                    ],
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Headcount settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffActivityHeadcount"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Active bookings (code attendee_mode_has_bookings)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "Invalid mode or party rules (code validation_failed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/activities/{id}/ticket-types": {
      "post": {
        "tags": [
          "Activities"
        ],
        "summary": "Add a ticket type",
        "description": "At most 10 per activity. Needs activity:write.",
        "operationId": "createTicketType",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffTicketTypeInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "ticketType": {
                      "$ref": "#/components/schemas/StaffTicketType"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid (code validation_failed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/activities/{id}/ticket-types/{ticket_type}": {
      "put": {
        "tags": [
          "Activities"
        ],
        "summary": "Change a ticket type",
        "description": "Bookings keep the lines they were booked with. Needs activity:write.",
        "operationId": "updateTicketType",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "ticket_type",
            "in": "path",
            "description": "Ticket type id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffTicketTypeInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "ticketType": {
                      "$ref": "#/components/schemas/StaffTicketType"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Activity or ticket type not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "Invalid (code validation_failed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Activities"
        ],
        "summary": "Remove a ticket type",
        "description": "Deleted when never booked, else archived. Needs activity:write.",
        "operationId": "deleteTicketType",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "ticket_type",
            "in": "path",
            "description": "Ticket type id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Archived or deleted",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "archived": {
                      "type": "boolean"
                    },
                    "deleted": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/activities/{id}/ticket-types/order": {
      "put": {
        "tags": [
          "Activities"
        ],
        "summary": "Reorder ticket types",
        "description": "Needs activity:write.",
        "operationId": "reorderTicketTypes",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "ticketTypeIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ticket types in the new order",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "ticketTypes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffTicketType"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid list (code validation_failed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/ticket-prices": {
      "put": {
        "tags": [
          "Sessions"
        ],
        "summary": "Set a session's ticket prices",
        "description": "Prices per ticket type of a headcount activity at this session; null for a ticket type falls back to the series, then the ticket type's default. Needs session:write:all.",
        "operationId": "setSessionTicketPrices",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "prices": {
                    "description": "Ticket type id => {amount, currency} or null",
                    "type": "object",
                    "additionalProperties": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/SessionMoney"
                        }
                      ],
                      "nullable": true
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The session's own ticket prices",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "ticketPrices": {
                      "type": "object",
                      "additionalProperties": {
                        "$ref": "#/components/schemas/SessionMoney"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "code not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "code validation_failed: unknown ticket type or invalid price",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/ticket-prices": {
      "put": {
        "tags": [
          "Series"
        ],
        "summary": "Set the ticket prices of a series",
        "description": "Ticket prices of a headcount activity for this series (#786), overriding the ticket types' default prices; its sessions without their own price follow them. Replaces all; a null price is left out. Needs series:write:all.",
        "operationId": "setSeriesTicketPrices",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "prices": {
                    "$ref": "#/components/schemas/SeriesSettingsTicketPrices"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The prices set",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "ticketPrices": {
                      "$ref": "#/components/schemas/SeriesSettingsTicketPrices"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Series not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Invalid prices (code validation_failed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/offer": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "What a party can book at a session",
        "description": "For staff booking a party: how the activity is booked, its party rules, the ticket types at this session's prices and the extras offered there. With the assigned scope only sessions the caller coaches. Needs booking:write.",
        "operationId": "getSessionPartyOffer",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The offer",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "offer": {
                      "properties": {
                        "sessionId": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "activityId": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "activityName": {
                          "type": "string",
                          "nullable": true
                        },
                        "locationId": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "attendeeMode": {
                          "type": "string",
                          "enum": [
                            "participants",
                            "headcount"
                          ]
                        },
                        "headcount": {
                          "description": "Party rules (min/max seats, phone required)",
                          "type": "object",
                          "nullable": true
                        },
                        "ticketTypes": {
                          "type": "array",
                          "items": {
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "name": {
                                "type": "string"
                              },
                              "description": {
                                "type": "string",
                                "nullable": true
                              },
                              "price": {
                                "$ref": "#/components/schemas/SessionMoney"
                              },
                              "seatWeight": {
                                "type": "integer"
                              },
                              "refundableOnLateCancel": {
                                "type": "boolean"
                              },
                              "minPerBooking": {
                                "type": "integer",
                                "nullable": true
                              },
                              "maxPerBooking": {
                                "type": "integer",
                                "nullable": true
                              }
                            },
                            "type": "object"
                          },
                          "nullable": true
                        },
                        "extras": {
                          "type": "array",
                          "items": {
                            "type": "object"
                          },
                          "nullable": true
                        },
                        "requiresBillingDetails": {
                          "type": "boolean"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookings/party": {
      "post": {
        "tags": [
          "Bookings"
        ],
        "summary": "Book a party by headcount",
        "description": "Staff book a party for a session of a headcount activity (#786): tickets per ticket type and optional extras (#793), for an existing customer or one found or created by email. Pending until paid, or confirmed with offlinePayment. Needs `booking:write` (assigned scope: sessions the member coaches; members limited to locations: sessions there). 409 `unavailable` (not enough seats); 422 `validation_failed`, `not_headcount`, `no_tickets`, `unknown_ticket_type`, `ticket_quantity`, `party_size`, `phone_required`, `unknown_extra`, `extra_quantity`, `mixed_currencies`, discount and currency codes.",
        "operationId": "bookStaffParty",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffPartyBookingInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Booked",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "booking": {
                      "$ref": "#/components/schemas/StaffPartyBooking"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Not enough seats (`unavailable`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Invalid party; `code` says why",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookings/{id}/party/preview": {
      "post": {
        "tags": [
          "Bookings"
        ],
        "summary": "Preview changing a party",
        "description": "Works out a change of a confirmed party's tickets/extras and/or a move to another session of the same activity (#797) without saving it. Needs `booking:write:all`. Same errors as PUT /bookings/{id}/party.",
        "operationId": "previewStaffPartyChange",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffPartyChangeInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What would change",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "change": {
                      "$ref": "#/components/schemas/StaffPartyChange"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`not_a_party`, `not_confirmed`, `changed`, `other_activity`, `not_bookable`, `unavailable`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Invalid tickets or extras; `code` says why",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookings/{id}/party": {
      "put": {
        "tags": [
          "Bookings"
        ],
        "summary": "Change a party",
        "description": "Saves the new tickets/extras of a confirmed party and/or moves it to another session of the same activity (#797). Booked lines keep their price, new ones take today's; the price difference is only returned, for staff to charge or refund by hand. A move clears the check-in and emails the customer unless notify is false. Audit-logged (booking.party_changed). Needs `booking:write:all`. 409 `not_a_party`, `not_confirmed`, `changed`, `other_activity`, `not_bookable`, `unavailable`; 422 ticket/extra codes.",
        "operationId": "changeStaffParty",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffPartyChangeInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Changed",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "change": {
                      "$ref": "#/components/schemas/StaffPartyChange"
                    },
                    "booking": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/StaffPartyBooking"
                        }
                      ],
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Not allowed; `code` says why",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Invalid tickets or extras; `code` says why",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/activities/{id}/booking-style": {
      "put": {
        "tags": [
          "Activities"
        ],
        "summary": "Booked at sessions, on request or at a free moment",
        "description": "instant: sessions; request: venue hire (#794, headcount only) with its hire settings; slots: at a free moment of its slot offers (#924). Audit-logged. Needs activity:write.",
        "operationId": "setActivityBookingStyle",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "bookingStyle": {
                    "type": "string",
                    "enum": [
                      "instant",
                      "request",
                      "slots"
                    ]
                  },
                  "hire": {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/ActivityHireSettings"
                      }
                    ],
                    "nullable": true,
                    "description": "Only for request"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Headcount settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffActivityHeadcount"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid style or hire settings (code validation_failed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/hires/{id}/check-in": {
      "post": {
        "tags": [
          "Venue hire"
        ],
        "summary": "Check in a hire's party",
        "description": "One mark for the whole party of a confirmed hire (#838): present (default) or absent; absent only once the event started, no fee. 409 invalid_status (not confirmed, or a no-show before the start), 422 invalid_attendance. Audit-logged (hire.attendance_marked). Needs attendance:write:all.",
        "operationId": "checkInHire",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Hire id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "status": {
                    "type": "string",
                    "default": "present",
                    "enum": [
                      "present",
                      "absent"
                    ]
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Marked",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "hireId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "attendance": {
                      "type": "string",
                      "enum": [
                        "present",
                        "absent",
                        "unmarked"
                      ]
                    },
                    "checkedInAt": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          },
          "409": {
            "description": "invalid_status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          },
          "422": {
            "description": "invalid_attendance",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Venue hire"
        ],
        "summary": "Clear a hire's check-in",
        "description": "Back to unmarked. Audit-logged. Needs attendance:write:all.",
        "operationId": "clearHireCheckIn",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Hire id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cleared",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "hireId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "attendance": {
                      "type": "string",
                      "enum": [
                        "present",
                        "absent",
                        "unmarked"
                      ]
                    },
                    "checkedInAt": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          },
          "409": {
            "description": "invalid_status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/hires/counts": {
      "get": {
        "tags": [
          "Venue hire"
        ],
        "summary": "Open hires per status",
        "description": "For a navigation badge (#840). Needs booking:read.",
        "operationId": "countOpenHires",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Counts",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "counts": {
                      "description": "status => number of open hires",
                      "type": "object",
                      "example": {
                        "new": 2,
                        "quoted": 1
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/hires/request-form/{activity_id}": {
      "get": {
        "tags": [
          "Venue hire"
        ],
        "summary": "The request form of a venue hire activity",
        "description": "The same data as the public request form (locations, day parts, extras, ticket types, rules), for any request activity, also unpublished, to take or edit a request (#840). 404 {code: not_requestable} for other activities. Needs booking:read.",
        "operationId": "getStaffHireRequestForm",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "activity_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The form as GET /api/public/{org_slug}/hire-requests/{id}/form, plus published",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "activity": {
                      "type": "object"
                    },
                    "headcount": {
                      "type": "object"
                    },
                    "ticketTypes": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "locations": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "timezone": {
                      "type": "string"
                    },
                    "earliestDate": {
                      "type": "string",
                      "format": "date"
                    },
                    "deposit": {
                      "type": "object"
                    },
                    "published": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Not a request activity (code not_requestable)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/hires": {
      "get": {
        "tags": [
          "Venue hire"
        ],
        "summary": "Venue hire requests",
        "description": "The requests inbox (#794), filtered by status (comma-separated), event date range and a search on contact, company or reference. Needs booking:read.",
        "operationId": "listHires",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "status",
            "in": "query",
            "description": "Comma-separated: new, quoted, accepted, confirmed, declined, expired, cancelled",
            "required": false,
            "schema": {
              "type": "string",
              "example": "new,quoted"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Hires",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "hires": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffHireSummary"
                      }
                    },
                    "counts": {
                      "description": "Open hires per status",
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Venue hire"
        ],
        "summary": "Take a hire request",
        "description": "A request taken by phone, under staff rules (no lead time). The client is customerId, or found or created from customer {email, firstName, lastName, phone?}. Codes: 422 validation_failed, unknown_day_part, day_part_closed, outside_day_part, invalid_date, invalid_time, party booking codes, billing_details_required, invalid_billing_details, answers_required; 404 not_requestable or unknown customer. Needs booking:write.",
        "operationId": "createHire",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "activityId",
                  "dayPartId",
                  "date",
                  "tickets"
                ],
                "properties": {
                  "customerId": {
                    "description": "An existing customer; else customer is required",
                    "type": "string",
                    "format": "uuid"
                  },
                  "customer": {
                    "properties": {
                      "email": {
                        "type": "string",
                        "format": "email"
                      },
                      "firstName": {
                        "type": "string"
                      },
                      "lastName": {
                        "type": "string"
                      },
                      "phone": {
                        "type": "string"
                      }
                    },
                    "type": "object"
                  },
                  "activityId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "dayPartId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "date": {
                    "type": "string",
                    "format": "date"
                  },
                  "startTime": {
                    "description": "Within the day part; default its start",
                    "type": "string",
                    "example": "18:00"
                  },
                  "endTime": {
                    "type": "string",
                    "example": "23:00"
                  },
                  "tickets": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/StaffHireTicketInput"
                    }
                  },
                  "extras": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/StaffBookingExtraInput"
                    }
                  },
                  "billingDetails": {
                    "$ref": "#/components/schemas/StaffBillingDetails"
                  },
                  "remarks": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Requested",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireResponse"
                }
              }
            }
          },
          "404": {
            "description": "Unknown customer, or not a request activity (not_requestable)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          },
          "422": {
            "description": "Refused (see codes)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/hires/{id}": {
      "get": {
        "tags": [
          "Venue hire"
        ],
        "summary": "One hire",
        "description": "The party, spaces, quote, charges, cancellation deadline, conflicts and guest capacity. Needs booking:read.",
        "operationId": "getHire",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Hire id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The hire",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Venue hire"
        ],
        "summary": "Change a hire",
        "description": "Fields left out stay as they are. spaceIds on a new or quoted request holds the spaces tentatively; on an accepted or confirmed hire it moves the hire to those spaces (old released, new taken; the quote is unchanged): 409 {code: space_conflict} when something is in the way. Audit-logged (hire.updated, hire.spaces_changed). Needs booking:write.",
        "operationId": "updateHire",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Hire id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "dayPartId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "date": {
                    "type": "string",
                    "format": "date"
                  },
                  "startTime": {
                    "type": "string",
                    "example": "18:00"
                  },
                  "endTime": {
                    "type": "string",
                    "example": "23:00"
                  },
                  "tickets": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/StaffHireTicketInput"
                    }
                  },
                  "extras": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/StaffBookingExtraInput"
                    }
                  },
                  "remarks": {
                    "type": "string",
                    "nullable": true
                  },
                  "internalNote": {
                    "type": "string",
                    "nullable": true
                  },
                  "spaceIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          },
          "409": {
            "description": "space_conflict, invalid_status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          },
          "422": {
            "description": "Refused (see codes)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/hires/{id}/quote/draft": {
      "get": {
        "tags": [
          "Venue hire"
        ],
        "summary": "Draft a quote",
        "description": "What Plek would quote (HireQuoteDrafter): ticket prices × quantities, the spaces' fee for the day part and per extra hour, a supplement below the spaces' minimum guests, the extras; with the deposit and how long the quote stays valid. Nothing is saved. Needs booking:read.",
        "operationId": "draftHireQuote",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Hire id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The draft",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "lines": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffHireLine"
                      }
                    },
                    "total": {
                      "$ref": "#/components/schemas/StaffAmount"
                    },
                    "deposit": {
                      "$ref": "#/components/schemas/StaffAmount"
                    },
                    "validUntil": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/hires/{id}/quote": {
      "post": {
        "tags": [
          "Venue hire"
        ],
        "summary": "Save or send the quote",
        "description": "Lines left out: the draft. Prices other than the draft's, or another deposit, need payment:write (403 payment_write_required). send: true emails the client the quote to accept online (status quoted). Spaces must be chosen first (409 invalid_spaces). Codes: invalid_line, invalid_quote, invalid_status. Audit-logged. Needs booking:write.",
        "operationId": "quoteHire",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Hire id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "lines": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/StaffHireLineInput"
                    }
                  },
                  "depositAmount": {
                    "description": "Cents; default the activity's deposit rule",
                    "type": "integer"
                  },
                  "validUntil": {
                    "type": "string",
                    "format": "date"
                  },
                  "send": {
                    "type": "boolean",
                    "default": false
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The hire with its quote",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireResponse"
                }
              }
            }
          },
          "403": {
            "description": "payment_write_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          },
          "404": {
            "description": "Not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          },
          "409": {
            "description": "invalid_spaces, invalid_status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          },
          "422": {
            "description": "invalid_line, invalid_quote",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/hires/{id}/accept": {
      "post": {
        "tags": [
          "Venue hire"
        ],
        "summary": "Accept a quote for the client",
        "description": "Takes the spaces (locked in id order; 409 space_conflict when a hire, block or booked shared session is in the way) and closes the shared sessions. withDeposit: false confirms at once without a deposit. paymentMethod: how the deposit is paid (default bank_transfer; 422 payment_method_unavailable). 409 quote_expired, invalid_status. Audit-logged. Needs booking:write.",
        "operationId": "acceptHire",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Hire id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "withDeposit": {
                    "type": "boolean",
                    "default": true
                  },
                  "paymentMethod": {
                    "type": "string",
                    "default": "bank_transfer",
                    "enum": [
                      "bank_transfer",
                      "on_site",
                      "online"
                    ]
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accepted (or confirmed without deposit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          },
          "409": {
            "description": "space_conflict, quote_expired, invalid_status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          },
          "422": {
            "description": "payment_method_unavailable, invalid_spaces",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/hires/{id}/decline": {
      "post": {
        "tags": [
          "Venue hire"
        ],
        "summary": "Decline a request",
        "description": "Releases held spaces and emails the client. Audit-logged. Needs booking:write.",
        "operationId": "declineHire",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Hire id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "reason": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Declined",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          },
          "409": {
            "description": "invalid_status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          },
          "422": {
            "description": "invalid_reason",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/hires/{id}/cancel": {
      "post": {
        "tags": [
          "Venue hire"
        ],
        "summary": "Cancel a hire",
        "description": "Releases the spaces (reopening shared sessions) and cancels open charges. Before the cancellation deadline the deposit is refunded (through the gateway, or owed when paid offline), after it kept; keepDeposit overrides that. Answers the refunds made. Audit-logged. Needs booking:write.",
        "operationId": "cancelHire",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Hire id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "reason": {
                    "type": "string"
                  },
                  "keepDeposit": {
                    "description": "Left out: by the deadline",
                    "type": "boolean",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "hire": {
                      "$ref": "#/components/schemas/StaffHire"
                    },
                    "refunds": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffHireRefund"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          },
          "409": {
            "description": "invalid_status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/hires/{id}/charges": {
      "post": {
        "tags": [
          "Venue hire"
        ],
        "summary": "Charge extras after the event",
        "description": "A hire_extra charge with its lines (type custom by default, in the hire's currency), due on dueDate (default per the rules). The client is emailed the lines and a pay button to Open amounts (#839). Codes: invalid_line (with index), invalid_status. Audit-logged. Needs payment:write.",
        "operationId": "addHireCharge",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Hire id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "lines"
                ],
                "properties": {
                  "description": {
                    "type": "string"
                  },
                  "lines": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/StaffHireLineInput"
                    }
                  },
                  "dueDate": {
                    "type": "string",
                    "format": "date"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Charged",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "charge": {
                      "$ref": "#/components/schemas/CustomerCharge"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          },
          "409": {
            "description": "invalid_status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          },
          "422": {
            "description": "invalid_line",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/hires/{id}/charges/{charge_id}/refund": {
      "post": {
        "tags": [
          "Venue hire"
        ],
        "summary": "Refund a paid hire charge",
        "description": "All (amount left out) or part of what was paid: through the gateway when paid online, else recorded as owed; manual: true records a refund made by hand. 422 invalid_refund when nothing is left to refund. Audit-logged. Needs payment:write.",
        "operationId": "refundHireCharge",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Hire id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "charge_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "amount": {
                    "description": "Cents; default what is left",
                    "type": "integer"
                  },
                  "note": {
                    "type": "string"
                  },
                  "manual": {
                    "type": "boolean",
                    "default": false
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Refunded",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "refund": {
                      "$ref": "#/components/schemas/StaffHireRefund"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Hire or charge not found (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          },
          "422": {
            "description": "invalid_refund",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/hires/{id}/charges/{charge_id}/receipt.pdf": {
      "get": {
        "tags": [
          "Venue hire"
        ],
        "summary": "Receipt of a paid hire charge",
        "description": "A payment confirmation PDF (#891), not an invoice: deposit and balance spread over the quote's VAT rates, extras per line. Needs payment:read.",
        "operationId": "getHireChargeReceipt",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Hire id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "charge_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The receipt",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "Hire, charge or receipt not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffHireError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/imports": {
      "get": {
        "tags": [
          "Imports and exports"
        ],
        "summary": "Recent imports",
        "description": "CSV imports of customers, participants and bookings, e.g. from Wix (#396). Needs customer:write:all.",
        "operationId": "listImports",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Imports",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "imports": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ImportJobSummary"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Imports and exports"
        ],
        "summary": "Upload a CSV to import",
        "description": "Parses the file and answers its headers, a column mapping suggested from them and a preview. Nothing is imported yet: dry-run, then run. Needs customer:write:all.",
        "operationId": "uploadImport",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "content"
                ],
                "properties": {
                  "fileName": {
                    "type": "string",
                    "default": "import.csv"
                  },
                  "content": {
                    "description": "The CSV text",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Uploaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportJob"
                }
              }
            }
          },
          "400": {
            "description": "No content, or not a readable CSV",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/imports/{id}": {
      "get": {
        "tags": [
          "Imports and exports"
        ],
        "summary": "One import",
        "description": "Poll it while a large import runs on the worker. Needs customer:write:all.",
        "operationId": "getImport",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Import id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The import",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportJob"
                }
              }
            }
          },
          "404": {
            "description": "Import not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/imports/{id}/dry-run": {
      "post": {
        "tags": [
          "Imports and exports"
        ],
        "summary": "Try an import",
        "description": "Runs the import with this mapping without saving anything; `report` says what would happen per row. Needs customer:write:all (and participant:write / booking:write for those columns).",
        "operationId": "dryRunImport",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Import id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportRunInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The import with its dry-run report",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportJob"
                }
              }
            }
          },
          "400": {
            "description": "Invalid mapping or options (the error says which)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Mapping participant or booking columns needs participant:write / booking:write (`requiredPermission`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Import not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "The import is already running",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/imports/{id}/run": {
      "post": {
        "tags": [
          "Imports and exports"
        ],
        "summary": "Run an import",
        "description": "Imports the rows. Up to 300 rows run at once (200); larger files are queued for the worker (202): poll the import until completed. Needs customer:write:all (and participant:write / booking:write for those columns).",
        "operationId": "runImport",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Import id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportRunInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Imported, with its report",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportJob"
                }
              }
            }
          },
          "202": {
            "description": "Queued for the worker",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportJob"
                }
              }
            }
          },
          "400": {
            "description": "Invalid mapping or options (the error says which)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Mapping participant or booking columns needs participant:write / booking:write (`requiredPermission`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Import not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "The import is already running",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/incidents": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "Incident reports of a session",
        "description": "Incidents and accidents reported for participants of the session; staff only. With the assigned scope only sessions the caller coaches. Needs attendance:read.",
        "operationId": "listSessionIncidents",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Incidents",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "incidents": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SessionIncident"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Report an incident in a session",
        "description": "Audit-logged (incident.created, without the text). Needs attendance:write; with the assigned scope only sessions the caller coaches.",
        "operationId": "reportSessionIncident",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SessionIncidentInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Reported",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "incident": {
                      "$ref": "#/components/schemas/SessionIncident"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "participantId is required (with `field`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "A field is invalid; `field` names it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/participants/{id}/incidents": {
      "get": {
        "tags": [
          "Participants"
        ],
        "summary": "Incident reports of a participant",
        "description": "Staff only, never shown to customers (#751). Needs `attendance:read` (assigned scope: participants the member coaches, reports of sessions they coach; members limited to locations: sessions there).",
        "operationId": "listParticipantIncidents",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Incidents",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "incidents": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffParticipantIncident"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/incidents/{id}": {
      "get": {
        "tags": [
          "Participants"
        ],
        "summary": "One incident report",
        "description": "Staff only, never shown to customers. Needs attendance:read; with the assigned scope only incidents in sessions the caller coaches.",
        "operationId": "getIncident",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Incident id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The incident",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "incident": {
                      "$ref": "#/components/schemas/SessionIncident"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Participants"
        ],
        "summary": "Complete or correct an incident report",
        "description": "Replaces every field of the report (participantId is ignored: the participant stays). Audit-logged (incident.updated, without the text). Needs attendance:write; with the assigned scope only incidents in sessions the caller coaches.",
        "operationId": "updateIncident",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Incident id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SessionIncidentInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "incident": {
                      "$ref": "#/components/schemas/SessionIncident"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "A field is invalid; `field` names it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Participants"
        ],
        "summary": "Delete an incident report",
        "description": "Audit-logged (incident.deleted). Needs attendance:write:all.",
        "operationId": "deleteIncident",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Incident id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/api-keys": {
      "get": {
        "tags": [
          "API keys"
        ],
        "summary": "List the organisation's API keys",
        "description": "Needs `integration:read`; staff only (never with an API key). Newest first, revoked and temporary test keys (#931) included.",
        "operationId": "listApiKeys",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Keys",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "apiKeys": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiKey"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "API keys"
        ],
        "summary": "Create an API key with a role",
        "description": "Needs `integration:write`; staff only. You can only give a role whose permissions you hold yourself (403). The secret is in this response only.",
        "operationId": "createApiKey",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "name",
                  "roleId"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 100
                  },
                  "roleId": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "apiKey": {
                      "$ref": "#/components/schemas/ApiKey"
                    },
                    "secret": {
                      "type": "string",
                      "example": "plek_live_..."
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/api-keys/{id}": {
      "delete": {
        "tags": [
          "API keys"
        ],
        "summary": "Revoke an API key",
        "description": "Needs `integration:write`; staff only. The key stops working at once. Audit-logged as `api_key.revoked`.",
        "operationId": "revokeApiKey",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "apiKey": {
                      "$ref": "#/components/schemas/ApiKey"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/webhooks": {
      "get": {
        "tags": [
          "Integrations"
        ],
        "summary": "Webhooks",
        "description": "The organisation's webhook endpoints and the events one can subscribe to (#404). Needs integration:read; staff only (never with an API key).",
        "operationId": "listWebhooks",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Webhooks",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "webhooks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookEventName"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Integrations"
        ],
        "summary": "Add a webhook",
        "description": "The signing secret is in this response only. Needs integration:write; staff only.",
        "operationId": "createWebhook",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "webhook": {
                      "$ref": "#/components/schemas/Webhook"
                    },
                    "secret": {
                      "description": "Shown once",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid url, events or description",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/webhooks/{id}": {
      "put": {
        "tags": [
          "Integrations"
        ],
        "summary": "Change a webhook",
        "description": "Replaces url, description, events and active (default true). Needs integration:write; staff only.",
        "operationId": "updateWebhook",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Webhook id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "webhook": {
                      "$ref": "#/components/schemas/Webhook"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid url, events, description or active",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Integrations"
        ],
        "summary": "Remove a webhook",
        "description": "Needs integration:write; staff only.",
        "operationId": "deleteWebhook",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Webhook id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Removed"
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/webhooks/{id}/secret": {
      "post": {
        "tags": [
          "Integrations"
        ],
        "summary": "New signing secret",
        "description": "The old secret stops signing at once; the new one is in this response only. Needs integration:write; staff only.",
        "operationId": "rotateWebhookSecret",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Webhook id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Rotated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "webhook": {
                      "$ref": "#/components/schemas/Webhook"
                    },
                    "secret": {
                      "description": "Shown once",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/webhooks/{id}/test": {
      "post": {
        "tags": [
          "Integrations"
        ],
        "summary": "Send a test event",
        "description": "Queues a `ping` delivery to the endpoint. Needs integration:write; staff only.",
        "operationId": "sendTestWebhook",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Webhook id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "delivery": {
                      "$ref": "#/components/schemas/WebhookDelivery"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/webhooks/{id}/deliveries": {
      "get": {
        "tags": [
          "Integrations"
        ],
        "summary": "Delivery log of a webhook",
        "description": "The latest 50 deliveries, newest first, with payload and response. Needs integration:read; staff only.",
        "operationId": "listWebhookDeliveries",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Webhook id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deliveries",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "deliveries": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookDelivery"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/webhooks/{id}/deliveries/{delivery_id}/redeliver": {
      "post": {
        "tags": [
          "Integrations"
        ],
        "summary": "Send a delivery again",
        "description": "Queues the same event (same payload and event id) again. Needs integration:write; staff only.",
        "operationId": "redeliverWebhook",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Webhook id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "delivery_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "delivery": {
                      "$ref": "#/components/schemas/WebhookDelivery"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Webhook or delivery not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/late-sign-up": {
      "get": {
        "tags": [
          "Series"
        ],
        "summary": "Late sign-up of a series",
        "description": "Whether a series that already started can still be booked, and at what price (#340): the override, the effective rule and the state now. Needs series:read:all.",
        "operationId": "getSeriesLateSignUp",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Late sign-up",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SeriesSettingsLateSignUp"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Series"
        ],
        "summary": "Set late sign-up of a series",
        "description": "The series' own late sign-up rule (#340); fields left out or null follow the booking rules, an empty body removes the override. Needs series:write:all.",
        "operationId": "setSeriesLateSignUp",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "allowed": {
                    "type": "boolean",
                    "nullable": true
                  },
                  "untilSession": {
                    "description": "Session number from 1",
                    "type": "integer",
                    "nullable": true
                  },
                  "pricing": {
                    "type": "string",
                    "nullable": true,
                    "enum": [
                      "pro_rated",
                      "full"
                    ]
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Late sign-up",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SeriesSettingsLateSignUp"
                }
              }
            }
          },
          "400": {
            "description": "Invalid pricing or untilSession",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/locations": {
      "get": {
        "tags": [
          "Locations"
        ],
        "summary": "Locations",
        "description": "The organisation's locations, page by page (page, per_page). Needs location:read.",
        "operationId": "listLocations",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Locations",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "locations": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffLocation"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/StaffPageMetadata"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Locations"
        ],
        "summary": "Add a location",
        "description": "Name and address (line 1, city, postal code, country) are required. A currency must be one the payment gateway takes (400 with a currency code otherwise). Needs location:write.",
        "operationId": "createLocation",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffLocationInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffLocationResponse"
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or a value is invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/locations/{id}": {
      "get": {
        "tags": [
          "Locations"
        ],
        "summary": "One location",
        "description": "Needs location:read.",
        "operationId": "getLocation",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Location id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The location",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffLocationResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Locations"
        ],
        "summary": "Change a location",
        "description": "Name and address are required again; currency, emailInfo and translations left out stay as they are. Needs location:write.",
        "operationId": "updateLocation",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Location id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffLocationInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffLocationResponse"
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or a value is invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Locations"
        ],
        "summary": "Remove a location",
        "description": "Needs location:write.",
        "operationId": "deleteLocation",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Location id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/locations/{id}/images": {
      "post": {
        "tags": [
          "Locations"
        ],
        "summary": "Add pictures to a location",
        "description": "Multipart upload of one or more images (field images or images[]). Needs location:write.",
        "operationId": "uploadLocationImages",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Location id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "properties": {
                  "images[]": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The location with its pictures",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffLocationResponse"
                }
              }
            }
          },
          "400": {
            "description": "No images, upload error or invalid file",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/locations/{id}/images/{image_id}": {
      "delete": {
        "tags": [
          "Locations"
        ],
        "summary": "Remove a picture of a location",
        "description": "Needs location:write.",
        "operationId": "deleteLocationImage",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Location id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "image_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The location without the picture",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffLocationResponse"
                }
              }
            }
          },
          "400": {
            "description": "Unknown location or picture",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/locations/{id}/images/reorder": {
      "patch": {
        "tags": [
          "Locations"
        ],
        "summary": "Order the pictures of a location",
        "description": "Needs location:write.",
        "operationId": "reorderLocationImages",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Location id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "orderedImageIds"
                ],
                "properties": {
                  "orderedImageIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The location",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffLocationResponse"
                }
              }
            }
          },
          "400": {
            "description": "orderedImageIds missing or not the location's pictures",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/locations/{id}/primary-image": {
      "patch": {
        "tags": [
          "Locations"
        ],
        "summary": "Choose the main picture of a location",
        "description": "Needs location:write.",
        "operationId": "setLocationPrimaryImage",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Location id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "imageId"
                ],
                "properties": {
                  "imageId": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The location",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffLocationResponse"
                }
              }
            }
          },
          "400": {
            "description": "imageId missing or unknown",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/minimum-participants": {
      "put": {
        "tags": [
          "Series"
        ],
        "summary": "Set the minimum participants of a series",
        "description": "A minimum checked (again) at its decision moment (#405); minimum null removes it. Needs series:write:all.",
        "operationId": "setSeriesMinimumParticipants",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "minimum": {
                    "properties": {
                      "minimum": {
                        "type": "integer"
                      },
                      "decisionHoursBefore": {
                        "description": "Hours before the start the minimum is checked",
                        "type": "integer"
                      },
                      "autoCancel": {
                        "description": "Cancel automatically when below the minimum",
                        "type": "boolean",
                        "default": false
                      }
                    },
                    "type": "object",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The minimum and the last check",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "minimumParticipants": {
                      "properties": {
                        "minimum": {
                          "type": "integer"
                        },
                        "decisionHoursBefore": {
                          "type": "integer"
                        },
                        "autoCancel": {
                          "type": "boolean"
                        }
                      },
                      "type": "object",
                      "nullable": true
                    },
                    "minimumCheck": {
                      "properties": {
                        "checkedAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "outcome": {
                          "type": "string",
                          "nullable": true,
                          "enum": [
                            "met",
                            "below_minimum",
                            "cancelled"
                          ]
                        }
                      },
                      "type": "object",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid minimum",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/minimum-participants": {
      "put": {
        "tags": [
          "Sessions"
        ],
        "summary": "Set a session's minimum participants",
        "description": "A new or changed minimum is checked (again) at its decision moment; null removes it. Needs session:write:all.",
        "operationId": "setSessionMinimumParticipants",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "minimum": {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/SessionMinimumParticipantsRule"
                      }
                    ],
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The minimum and its check",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "minimumParticipants": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/SessionMinimumParticipantsRule"
                        }
                      ],
                      "nullable": true
                    },
                    "minimumCheck": {
                      "properties": {
                        "checkedAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "outcome": {
                          "type": "string",
                          "nullable": true
                        }
                      },
                      "type": "object",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid minimum",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/payment-gateways/mollie/connect": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "Is Mollie Connect available",
        "description": "Whether this Plek installation has a Mollie app, so staff can connect Mollie through its login instead of an API key (#910). Needs payment_gateway:read.",
        "operationId": "getMollieConnectOptions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Availability",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "available": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Start connecting Mollie",
        "description": "Answers Mollie's consent URL to send the browser to; Mollie returns to Plek, which redirects to the staff app's gateway page (?mollie=connected or choose_profile, or ?mollie_error=). gatewayId reconnects a gateway or switches an API-key gateway; signup opens Mollie's sign-up page. Errors (code): 404 not_found (gateway), 422 not_configured. Staff sign-in only (the state is bound to the staff member). Needs payment_gateway:write.",
        "operationId": "startMollieConnect",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "gatewayId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  },
                  "name": {
                    "description": "Name of a new gateway",
                    "type": "string",
                    "nullable": true
                  },
                  "signup": {
                    "type": "boolean",
                    "default": false
                  },
                  "locale": {
                    "type": "string",
                    "example": "nl_BE",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Consent URL",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "authorizationUrl": {
                      "type": "string",
                      "format": "uri"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Gateway not found (not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "Mollie Connect is not set up here (not_configured)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/payment-gateways/{id}/mollie": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "Mollie connection of a gateway",
        "description": "The connection, the Mollie organisation, its website profiles and onboarding, read live from Mollie. error is set (e.g. reconnect_needed, mollie_unreachable) when Mollie could not be read. 404 not_found for a gateway that isn't Mollie. Needs payment_gateway:read.",
        "operationId": "getMollieConnection",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Payment gateway id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The connection",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "connected": {
                      "type": "boolean"
                    },
                    "available": {
                      "description": "Mollie Connect is set up here",
                      "type": "boolean"
                    },
                    "needsReconnect": {
                      "type": "boolean"
                    },
                    "organization": {
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string",
                          "nullable": true
                        }
                      },
                      "type": "object"
                    },
                    "connectedAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "profileId": {
                      "type": "string",
                      "nullable": true
                    },
                    "profiles": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "website": {
                            "type": "string",
                            "nullable": true
                          },
                          "status": {
                            "type": "string",
                            "nullable": true
                          }
                        },
                        "type": "object"
                      },
                      "nullable": true
                    },
                    "onboarding": {
                      "properties": {
                        "status": {
                          "type": "string"
                        },
                        "canReceivePayments": {
                          "type": "boolean"
                        },
                        "canReceiveSettlements": {
                          "type": "boolean"
                        },
                        "dashboardUrl": {
                          "type": "string",
                          "nullable": true
                        }
                      },
                      "type": "object",
                      "nullable": true
                    },
                    "error": {
                      "type": "string",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Not a Mollie gateway of this organisation (not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/payment-gateways/{id}/mollie/profile": {
      "put": {
        "tags": [
          "Payments"
        ],
        "summary": "Pick the Mollie profile",
        "description": "The website profile of the connected Mollie account to take payments for; activates a new connection. Audit payment_gateway.mollie_profile_chosen. Errors (code): 404 not_found, 422 validation_failed, unknown_profile, not_connected, reconnect_needed, 502 mollie_unreachable. Needs payment_gateway:write.",
        "operationId": "chooseMollieProfile",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Payment gateway id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "profileId"
                ],
                "properties": {
                  "profileId": {
                    "type": "string",
                    "example": "pfl_QkEhN94Ba"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Gateway",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "gateway": {
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "isActive": {
                          "type": "boolean"
                        },
                        "configuration": {
                          "type": "object"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Not found (not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "Invalid or unknown profile",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "502": {
            "description": "Mollie could not be reached (mollie_unreachable)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/oauth-connections": {
      "get": {
        "tags": [
          "Integrations"
        ],
        "summary": "Connected apps of every member",
        "description": "Apps members connected over OAuth (#815), e.g. an AI assistant on the MCP server, with the member who connected each. Needs integration:read; staff only (never with an API key).",
        "operationId": "listOrganisationOAuthConnections",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Connections",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "connections": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OAuthConnection"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/oauth-connections/{id}": {
      "delete": {
        "tags": [
          "Integrations"
        ],
        "summary": "Disconnect a member's app",
        "description": "Revokes the grant: its tokens stop working at once. Audit-logged as oauth.revoked (reason admin). Needs integration:write; staff only.",
        "operationId": "revokeOrganisationOAuthConnection",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Connection (grant) id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Disconnected"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/offline-payments": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "Paying later: settings",
        "description": "Pay on site and bank transfer, deadline and reminders (#397, #723). Needs payment_gateway:read.",
        "operationId": "getOfflinePaymentSettings",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Settings",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "settings": {
                      "$ref": "#/components/schemas/OfflinePaymentSettings"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Payments"
        ],
        "summary": "Paying later: change settings",
        "description": "Replaces the settings; fields left out take their default. 400 for an invalid IBAN (bank transfer needs one), a deadline outside 0..365, a reminder outside 0..60 or more than 3 overdue reminder days. Needs payment_gateway:write.",
        "operationId": "setOfflinePaymentSettings",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OfflinePaymentSettings"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Settings",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "settings": {
                      "$ref": "#/components/schemas/OfflinePaymentSettings"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookings/{booking_id}/payments": {
      "get": {
        "tags": [
          "Bookings"
        ],
        "summary": "Payments of a booking",
        "description": "Online payments and payments recorded by staff, with their refunds. Needs `payment:read`.",
        "operationId": "listBookingPayments",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "booking_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Payments",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "payments": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffBookingPayment"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Bookings"
        ],
        "summary": "Record a payment received outside the gateway",
        "description": "Cash, card terminal, bank transfer or other, received by staff (#397). Confirms a pending booking once paid. Needs `payment:write`.",
        "operationId": "recordBookingPayment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "booking_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "amount",
                  "method"
                ],
                "properties": {
                  "amount": {
                    "description": "In cents",
                    "type": "integer",
                    "minimum": 1
                  },
                  "currency": {
                    "description": "Default: the booking's currency",
                    "type": "string"
                  },
                  "method": {
                    "type": "string",
                    "enum": [
                      "cash",
                      "cardterminal",
                      "banktransfer",
                      "other"
                    ]
                  },
                  "paidAt": {
                    "description": "Default: now",
                    "type": "string",
                    "format": "date-time",
                    "nullable": true
                  },
                  "note": {
                    "type": "string",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Recorded",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "payment": {
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "amount": {
                          "$ref": "#/components/schemas/StaffBookingAmount"
                        },
                        "method": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string"
                        },
                        "paidAt": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true
                        }
                      },
                      "type": "object"
                    },
                    "balance": {
                      "$ref": "#/components/schemas/StaffBookingBalance"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid amount, method, currency or date",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookings/{booking_id}/payment-reminders": {
      "post": {
        "tags": [
          "Bookings"
        ],
        "summary": "Send the payment reminder now",
        "description": "For a confirmed booking paid later with something outstanding (#723). Audit-logged. Needs `payment:write`. 409 `not_awaiting_payment`; 502 `send_failed` when the email could not be sent.",
        "operationId": "sendBookingPaymentReminder",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "booking_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sent",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "reminderSentAt": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Nothing to pay later (`not_awaiting_payment`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "The email could not be sent (`send_failed`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/public/{org_slug}/payment-options": {
      "get": {
        "tags": [
          "Public - Booking form"
        ],
        "summary": "Ways to pay the booking flow offers",
        "description": "Online (an active payment gateway), on site and by bank transfer (#397; bank transfer only with an IBAN set).",
        "operationId": "publicGetPaymentOptions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The options",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicFormPaymentOptions"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/{org_slug}/bookings/{id}/pay-offline": {
      "post": {
        "tags": [
          "Public - Bookings"
        ],
        "summary": "Pay later, on site or by bank transfer",
        "description": "Instead of paying online, the customer pays on site or by bank transfer (#397): confirms the booking and the other bookings of its checkout, with a payment reference and deadline. 400 when the method is unknown or not offered by the organisation, or the booking no longer awaits payment; 404 for an unknown booking.",
        "operationId": "publicPayBookingOffline",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "A booking of the checkout",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "method"
                ],
                "properties": {
                  "method": {
                    "type": "string",
                    "enum": [
                      "on_site",
                      "bank_transfer"
                    ]
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirmed, with how to pay",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "bookings": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "status": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      }
                    },
                    "payment": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/PublicFormOfflinePayment"
                        }
                      ],
                      "nullable": true
                    },
                    "amount": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/PublicFormAmount"
                        }
                      ],
                      "nullable": true,
                      "description": "Total still to pay for the checkout"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/org/{organisation_id}/onboarding/checklist": {
      "get": {
        "tags": [
          "Organisation settings"
        ],
        "summary": "The \"Get started\" checklist",
        "description": "The dashboard's setup steps (#412, #740), whether each is done or skipped. Needs organisation:read.",
        "operationId": "getOnboardingChecklist",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Steps",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "steps": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OrgSettingsOnboardingStep"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/onboarding/checklist/{step}": {
      "put": {
        "tags": [
          "Organisation settings"
        ],
        "summary": "Skip or unskip a checklist step",
        "description": "Needs organisation:write.",
        "operationId": "skipOnboardingStep",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "step",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "location",
                "activity",
                "series",
                "activate",
                "payments",
                "terms",
                "share",
                "cancellation_policy",
                "reminders",
                "email_sender",
                "closures",
                "team",
                "customers"
              ]
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "skipped": {
                    "type": "boolean",
                    "default": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Steps",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "steps": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OrgSettingsOnboardingStep"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Unknown step (code unknown_step)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/members": {
      "get": {
        "tags": [
          "Team"
        ],
        "summary": "Team members",
        "description": "Every member with their role and the locations they are limited to; with member:read:assigned only the caller's own membership. Needs member:read.",
        "operationId": "listMembers",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Members",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "members": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TeamMember"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/coaches": {
      "get": {
        "tags": [
          "Team"
        ],
        "summary": "Members who can coach",
        "description": "Every member, for picking the coaches of a session or series (#381). Needs member:read:all.",
        "operationId": "listCoaches",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Members",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "coaches": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "description": "User id",
                            "type": "string",
                            "format": "uuid"
                          },
                          "email": {
                            "type": "string"
                          },
                          "firstName": {
                            "type": "string"
                          },
                          "lastName": {
                            "type": "string"
                          },
                          "fullName": {
                            "type": "string"
                          },
                          "role": {
                            "description": "Role name",
                            "type": "string",
                            "nullable": true
                          },
                          "isActive": {
                            "type": "boolean"
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/members/{membership_id}/role": {
      "patch": {
        "tags": [
          "Team"
        ],
        "summary": "Change a member's role",
        "description": "A member has exactly one role; there is always at least one Admin (409, type role_rule_violation). Members can't change their own role (403). Needs member:write.",
        "operationId": "updateMemberRole",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "membership_id",
            "in": "path",
            "description": "Membership id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "roleId"
                ],
                "properties": {
                  "roleId": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The member",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamMember"
                }
              }
            }
          },
          "400": {
            "description": "roleId missing or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Your own role",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The last Admin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamDomainError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/members/{membership_id}": {
      "delete": {
        "tags": [
          "Team"
        ],
        "summary": "Remove a member",
        "description": "Ends the membership; the user account stays. Not yourself (403), not the last Admin (409, type role_rule_violation). Needs member:write.",
        "operationId": "removeMember",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "membership_id",
            "in": "path",
            "description": "Membership id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "403": {
            "description": "Yourself",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The last Admin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamDomainError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/members/invite": {
      "post": {
        "tags": [
          "Team"
        ],
        "summary": "Invite someone to the team",
        "description": "Emails an invitation to join with a role (#372); they accept through the link, signed in or by registering. 409 type invitation_already_member when the address is already a member. Needs member:write.",
        "operationId": "inviteMember",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "email",
                  "roleId"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "roleId": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Invited",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "invitation": {
                      "$ref": "#/components/schemas/TeamInvitation"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "email or roleId missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Unknown role",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamDomainError"
                }
              }
            }
          },
          "409": {
            "description": "Already a member",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamDomainError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/members/{membership_id}/locations": {
      "put": {
        "tags": [
          "Team"
        ],
        "summary": "Limit a member to locations",
        "description": "restricted: true limits the member to locationIds, false lets them reach every location again (#938). Refused with 409 code location_roles_disabled until location roles are switched on; 422 codes admin_unrestricted, no_locations, unknown_location. Not your own (403). Needs member:write.",
        "operationId": "updateMemberLocations",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "membership_id",
            "in": "path",
            "description": "Membership id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "restricted"
                ],
                "properties": {
                  "restricted": {
                    "type": "boolean"
                  },
                  "locationIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The membership",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "membership": {
                      "$ref": "#/components/schemas/TeamMembership"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "restricted or locationIds invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Your own locations",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Location roles not available yet (code location_roles_disabled)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamDomainError"
                }
              }
            }
          },
          "422": {
            "description": "admin_unrestricted, no_locations or unknown_location",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamDomainError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/participants": {
      "get": {
        "tags": [
          "Participants"
        ],
        "summary": "List participants",
        "description": "Paginated, searched and sorted in the database. Needs `participant:read` (assigned scope: only participants booked into sessions the member coaches, with the coach view of each participant).",
        "operationId": "listParticipants",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Matches the name",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "name",
              "enum": [
                "name",
                "dateOfBirth",
                "createdAt"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Participants",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "participants": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffParticipant"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/StaffBookingPagination"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid sort or direction",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Participants"
        ],
        "summary": "Add a participant",
        "description": "Needs `participant:write:all`.",
        "operationId": "createParticipant",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffParticipantInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "participant": {
                      "$ref": "#/components/schemas/StaffParticipant"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or a value is invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/participants/{id}": {
      "get": {
        "tags": [
          "Participants"
        ],
        "summary": "One participant",
        "description": "With the custom field values and their labels. Needs `participant:read` (assigned scope: participants booked into sessions the member coaches, coach view).",
        "operationId": "getParticipant",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The participant",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "participant": {
                      "$ref": "#/components/schemas/StaffParticipantDetail"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid participant ID",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Participants"
        ],
        "summary": "Change a participant",
        "description": "Replaces the participant's fields (customerIds are linked separately). Needs `participant:write` (assigned scope: participants booked into sessions the member coaches).",
        "operationId": "updateParticipant",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffParticipantInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "participant": {
                      "$ref": "#/components/schemas/StaffParticipant"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or a value is invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Participants"
        ],
        "summary": "Delete a participant",
        "description": "Needs `participant:write` (assigned scope: participants booked into sessions the member coaches).",
        "operationId": "deleteParticipant",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/participants/{id}/customers": {
      "post": {
        "tags": [
          "Participants"
        ],
        "summary": "Link a customer to a participant",
        "description": "The customer account then manages this participant too. Needs `participant:write` (assigned scope: participants booked into sessions the member coaches).",
        "operationId": "linkParticipantCustomer",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "customerId"
                ],
                "properties": {
                  "customerId": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Linked",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "participant": {
                      "$ref": "#/components/schemas/StaffParticipant"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "customerId is required, or the link was refused",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/participants/{id}/customers/{customer_id}": {
      "delete": {
        "tags": [
          "Participants"
        ],
        "summary": "Unlink a customer from a participant",
        "description": "Needs `participant:write` (assigned scope: participants booked into sessions the member coaches).",
        "operationId": "unlinkParticipantCustomer",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Unlinked",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "participant": {
                      "$ref": "#/components/schemas/StaffParticipant"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "The unlink was refused",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/participants/duplicates": {
      "get": {
        "tags": [
          "Participants"
        ],
        "summary": "Possible duplicate participants",
        "description": "Participants flagged as a possible duplicate (#426), each with its customers and likely matches. Needs `participant:write:all`.",
        "operationId": "listDuplicateParticipants",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Duplicates",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "duplicates": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "participant": {
                            "$ref": "#/components/schemas/StaffParticipantMergeView"
                          },
                          "customers": {
                            "type": "array",
                            "items": {
                              "properties": {
                                "id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "fullName": {
                                  "type": "string"
                                },
                                "email": {
                                  "type": "string"
                                }
                              },
                              "type": "object"
                            }
                          },
                          "candidates": {
                            "type": "array",
                            "items": {
                              "properties": {
                                "participant": {
                                  "$ref": "#/components/schemas/StaffParticipantMergeView"
                                },
                                "match": {
                                  "type": "string",
                                  "enum": [
                                    "same_name_and_birth_date",
                                    "same_first_name",
                                    "similar_name",
                                    "same_customer"
                                  ]
                                }
                              },
                              "type": "object"
                            }
                          }
                        },
                        "type": "object"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/StaffBookingPagination"
                    },
                    "stats": {
                      "properties": {
                        "periodDays": {
                          "type": "integer",
                          "example": 30
                        },
                        "merged": {
                          "description": "Merges in that period",
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/participants/{id}/merge-preview/{target_id}": {
      "get": {
        "tags": [
          "Participants"
        ],
        "summary": "Preview merging a participant into another",
        "description": "Both participants side by side with what points at them. Needs `participant:write:all`.",
        "operationId": "previewParticipantMerge",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "The duplicate",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "target_id",
            "in": "path",
            "description": "The participant it is merged into",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Preview",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "duplicate": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/StaffParticipantMergeView"
                        },
                        {
                          "properties": {
                            "references": {
                              "$ref": "#/components/schemas/StaffParticipantReferences"
                            }
                          },
                          "type": "object"
                        }
                      ]
                    },
                    "target": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/StaffParticipantMergeView"
                        },
                        {
                          "properties": {
                            "references": {
                              "$ref": "#/components/schemas/StaffParticipantReferences"
                            }
                          },
                          "type": "object"
                        }
                      ]
                    },
                    "sameCustomer": {
                      "type": "boolean"
                    },
                    "mergeableFields": {
                      "description": "Fields that can be taken from the duplicate (useDuplicateFor), besides `customFields.<name>`",
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/participants/{id}/merge": {
      "post": {
        "tags": [
          "Participants"
        ],
        "summary": "Merge a participant into another",
        "description": "Moves the duplicate's bookings, check-ins, waiting list entries and the like to the target and removes the duplicate. The target's values win except for the fields in useDuplicateFor. Audit-logged. Needs `participant:write:all`. 422 `not_mergeable`.",
        "operationId": "mergeParticipant",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "The duplicate",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "targetParticipantId"
                ],
                "properties": {
                  "targetParticipantId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "useDuplicateFor": {
                    "description": "mergeableFields or `customFields.<name>` to take from the duplicate",
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Merged",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "participant": {
                      "$ref": "#/components/schemas/StaffParticipantMergeView"
                    },
                    "moved": {
                      "description": "What moved: kind => count",
                      "type": "object",
                      "additionalProperties": {
                        "type": "integer"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "targetParticipantId is required, or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Cannot be merged (`not_mergeable`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/participants/{id}/not-duplicate": {
      "post": {
        "tags": [
          "Participants"
        ],
        "summary": "Mark a participant as not a duplicate",
        "description": "Clears the possible-duplicate flag. Needs `participant:write:all`.",
        "operationId": "dismissDuplicateParticipant",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Flag cleared",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "participant": {
                      "$ref": "#/components/schemas/StaffParticipantMergeView"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/payments/{id}": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "One payment",
        "description": "An online payment or one recorded by staff, with its refunds. Needs payment:read.",
        "operationId": "getPayment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Payment id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The payment",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "payment": {
                      "$ref": "#/components/schemas/StaffBookingPayment"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/payments/{id}/refund": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Refund a payment",
        "description": "All of what is refundable, or amount cents of it. Online payments are refunded through the gateway; a manual payment (paid outside the gateway) is only recorded as refunded, paid back by hand (#784). 400 for an invalid amount or a payment that cannot be refunded. Needs payment:refund.",
        "operationId": "refundPayment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Payment id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "amount": {
                    "description": "Cents; left out: everything refundable",
                    "type": "integer",
                    "nullable": true,
                    "minimum": 1
                  },
                  "description": {
                    "type": "string",
                    "nullable": true,
                    "maxLength": 255
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Refunded",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "payment": {
                      "$ref": "#/components/schemas/StaffBookingPayment"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid amount or not refundable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/payment-gateways/{id}/test": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Test a payment gateway",
        "description": "Checks the stored credentials with the provider without moving money (#832). Errors (code): 422 not_supported (this gateway type cannot be tested), incomplete (fill in every required field and activate it first). Needs payment_gateway:write.",
        "operationId": "testPaymentGateway",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Payment gateway id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The checks done",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "checks": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "check": {
                            "type": "string"
                          },
                          "ok": {
                            "type": "boolean"
                          },
                          "code": {
                            "type": "string"
                          },
                          "detail": {
                            "type": "string",
                            "nullable": true
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "not_supported or incomplete",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/payment-gateways": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "Payment gateways",
        "description": "The organisation's payment gateways; credentials are masked. Needs payment_gateway:read.",
        "operationId": "listPaymentGateways",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Gateways",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "gateways": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PaymentGateway"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Add a payment gateway",
        "description": "With an API key or other credentials (Mollie can also connect through Mollie Connect: POST /payment-gateways/mollie/connect). 400 when a field is missing, the type is unknown or the configuration lacks a required setting. Needs payment_gateway:write.",
        "operationId": "createPaymentGateway",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentGatewayInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "gateway": {
                      "$ref": "#/components/schemas/PaymentGateway"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/payment-gateways/{id}": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "One payment gateway",
        "description": "Needs payment_gateway:read.",
        "operationId": "getPaymentGateway",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Payment gateway id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The gateway",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "gateway": {
                      "$ref": "#/components/schemas/PaymentGateway"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Payments"
        ],
        "summary": "Change a payment gateway",
        "description": "Name and configuration (type is fixed). A secret left out or empty keeps the stored one; a Mollie Connect gateway only changes its name and testmode. 400 when the configuration lacks a required setting. Needs payment_gateway:write.",
        "operationId": "updatePaymentGateway",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Payment gateway id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "name",
                  "configuration"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "configuration": {
                    "type": "object"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "gateway": {
                      "$ref": "#/components/schemas/PaymentGateway"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Payments"
        ],
        "summary": "Remove a payment gateway",
        "description": "A Mollie Connect gateway also revokes the authorisation at Mollie. Audit-logged. Needs payment_gateway:write.",
        "operationId": "deletePaymentGateway",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Payment gateway id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/plans": {
      "get": {
        "tags": [
          "Plans and entitlements"
        ],
        "summary": "Plans",
        "description": "Subscriptions and passes, with how many were sold. Needs plan:read.",
        "operationId": "listPlans",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "subscription",
                "pass"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Plans",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "plans": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffPlan"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Plans and entitlements"
        ],
        "summary": "Create a plan",
        "description": "A subscription (services with a rule, bound to one participant) or pass (credits shared by the customer's participants). 400 for invalid input (unknown activities, currency, duration, translations). Needs plan:write.",
        "operationId": "createPlan",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffPlanInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffPlan"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/plans/{id}": {
      "get": {
        "tags": [
          "Plans and entitlements"
        ],
        "summary": "One plan",
        "description": "Needs plan:read.",
        "operationId": "getPlan",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Plan id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The plan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffPlan"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Plans and entitlements"
        ],
        "summary": "Change a plan",
        "description": "Same body as create (kind stays). What was already sold keeps its terms. Needs plan:write.",
        "operationId": "updatePlan",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Plan id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffPlanInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffPlan"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Plans and entitlements"
        ],
        "summary": "Delete a plan",
        "description": "Deleted when nothing was ever granted from it, otherwise only taken off sale. Needs plan:write.",
        "operationId": "deletePlan",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Plan id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "What happened",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "deleted": {
                      "type": "boolean"
                    },
                    "deactivated": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/pos/options": {
      "get": {
        "tags": [
          "Point of sale"
        ],
        "summary": "What can be sold at the desk",
        "description": "Sessions of the next 14 days not yet ended (a member limited to locations sees theirs only), plans on sale and gift card values. Needs booking:write:all and payment:write.",
        "operationId": "getPosOptions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Options",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "currency": {
                      "type": "string"
                    },
                    "onlinePayments": {
                      "description": "An active gateway exists, so a pay link can be sent",
                      "type": "boolean"
                    },
                    "giftCards": {
                      "properties": {
                        "enabled": {
                          "type": "boolean"
                        },
                        "denominations": {
                          "type": "array",
                          "items": {
                            "type": "integer"
                          }
                        }
                      },
                      "type": "object"
                    },
                    "sessions": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "activityName": {
                            "type": "string",
                            "nullable": true
                          },
                          "attendeeMode": {
                            "type": "string",
                            "enum": [
                              "participants",
                              "headcount"
                            ]
                          },
                          "locationName": {
                            "type": "string",
                            "nullable": true
                          },
                          "startTime": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "endTime": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "price": {
                            "$ref": "#/components/schemas/StaffAmount"
                          },
                          "spotsLeft": {
                            "type": "integer"
                          }
                        },
                        "type": "object"
                      }
                    },
                    "plans": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "kind": {
                            "type": "string",
                            "enum": [
                              "subscription",
                              "pass"
                            ]
                          },
                          "name": {
                            "type": "string"
                          },
                          "price": {
                            "$ref": "#/components/schemas/StaffAmount"
                          },
                          "forParticipant": {
                            "type": "boolean"
                          },
                          "fixedStartDate": {
                            "type": "string",
                            "format": "date",
                            "nullable": true
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/pos/quote": {
      "post": {
        "tags": [
          "Point of sale"
        ],
        "summary": "Price a desk sale",
        "description": "The lines and total of a sale, with the discount code applied (discountCodeError says why it was not). Nothing is booked. 404 unknown customer or session, 422 invalid items. Needs booking:write:all and payment:write.",
        "operationId": "quotePosSale",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "customerId",
                  "items"
                ],
                "properties": {
                  "customerId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "items": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/PosSaleItemInput"
                    },
                    "maxItems": 20,
                    "minItems": 1
                  },
                  "discountCode": {
                    "type": "string",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quote",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "lines": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PosLine"
                      }
                    },
                    "total": {
                      "$ref": "#/components/schemas/StaffAmount"
                    },
                    "discountCodeError": {
                      "type": "string",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid items (code for currency problems)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/pos/sales": {
      "post": {
        "tags": [
          "Point of sale"
        ],
        "summary": "Sell at the desk",
        "description": "Books the sessions, grants or reserves the plans and issues the gift cards in one go. cash and cardterminal are recorded as paid (staff = the member); link creates online payments and answers their pages in links. sendEmail emails the customer the confirmation. Errors: 404 unknown customer or session (code not_found), 409 a party no longer fits, 422 invalid items, age requirement, plan not available (code), already_booked, not_bookable and currency codes. Needs booking:write:all and payment:write.",
        "operationId": "createPosSale",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "customerId",
                  "items",
                  "paymentMethod"
                ],
                "properties": {
                  "customerId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "items": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/PosSaleItemInput"
                    },
                    "maxItems": 20,
                    "minItems": 1
                  },
                  "discountCode": {
                    "type": "string",
                    "nullable": true
                  },
                  "paymentMethod": {
                    "type": "string",
                    "enum": [
                      "cash",
                      "cardterminal",
                      "link"
                    ]
                  },
                  "sendEmail": {
                    "type": "boolean",
                    "default": false
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Sold",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "sale": {
                      "$ref": "#/components/schemas/PosSale"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "No longer available",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "Invalid sale",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/customers/{id}/personal-data": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Everything stored about a customer",
        "description": "GDPR export (#400) as a JSON attachment; audit-logged (customer.data_exported). Needs privacy:read.",
        "operationId": "exportCustomerPersonalData",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Customer id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The personal data (Content-Disposition: attachment)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/customers/{id}/anonymise": {
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Anonymise a customer",
        "description": "GDPR erasure (#400): personal data removed, bookings and payments kept anonymously. Refused with upcoming bookings unless force. Needs privacy:write.",
        "operationId": "anonymiseCustomer",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Customer id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "force": {
                    "description": "Also with upcoming bookings",
                    "type": "boolean",
                    "default": false
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was anonymised",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "anonymised": {
                      "description": "Counts per kind of record",
                      "type": "object",
                      "additionalProperties": {
                        "type": "integer"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Upcoming bookings (code upcoming_bookings)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/privacy/settings": {
      "get": {
        "tags": [
          "Organisation settings"
        ],
        "summary": "Retention periods",
        "description": "How long personal data is kept (#400). Needs privacy:read.",
        "operationId": "getPrivacySettings",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Retention periods",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrgSettingsRetention"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Organisation settings"
        ],
        "summary": "Change the retention periods",
        "description": "Whole numbers 1 to 120; null, 0 or left out switches a period off. Audit-logged (retention.settings_changed). Needs privacy:write.",
        "operationId": "updatePrivacySettings",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrgSettingsRetention"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Retention periods",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrgSettingsRetention"
                }
              }
            }
          },
          "422": {
            "description": "Not a whole number, or out of range",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/privacy/log": {
      "get": {
        "tags": [
          "Organisation settings"
        ],
        "summary": "The privacy log",
        "description": "Who exported or anonymised what, and retention runs: the last 200 entries, newest first. Needs privacy:read.",
        "operationId": "getPrivacyLog",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Entries",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "entries": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OrgSettingsPrivacyLogEntry"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/public/{org_slug}/activities": {
      "get": {
        "tags": [
          "Public - Activities"
        ],
        "summary": "List published activities",
        "description": "Retrieve all published activities for an organisation. Only activities marked as published will be returned.",
        "operationId": "listPublicActivities",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "List of activities",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "activities": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicActivity"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/activities/{id}": {
      "get": {
        "tags": [
          "Public - Activities"
        ],
        "summary": "Get activity details",
        "description": "Retrieve details of a specific published activity.",
        "operationId": "getPublicActivity",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "description": "Activity details",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "activity": {
                      "$ref": "#/components/schemas/PublicActivity"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/bookings/session": {
      "post": {
        "tags": [
          "Public - Bookings"
        ],
        "summary": "Create a session booking",
        "description": "Create a booking for a single session. This endpoint handles guest checkout by creating/finding customer and participant records automatically.",
        "operationId": "createPublicSessionBooking",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "sessionId",
                  "customer",
                  "participant"
                ],
                "properties": {
                  "sessionId": {
                    "description": "Session to book",
                    "type": "string",
                    "format": "uuid"
                  },
                  "customer": {
                    "required": [
                      "email",
                      "firstName",
                      "lastName"
                    ],
                    "properties": {
                      "email": {
                        "type": "string",
                        "format": "email"
                      },
                      "firstName": {
                        "type": "string"
                      },
                      "lastName": {
                        "type": "string"
                      },
                      "phone": {
                        "type": "string",
                        "nullable": true
                      },
                      "nationalRegistryNumber": {
                        "type": "string",
                        "nullable": true
                      }
                    },
                    "type": "object"
                  },
                  "participant": {
                    "required": [
                      "firstName",
                      "lastName"
                    ],
                    "properties": {
                      "firstName": {
                        "type": "string"
                      },
                      "lastName": {
                        "type": "string"
                      },
                      "nationalRegistryNumber": {
                        "type": "string",
                        "nullable": true
                      },
                      "tshirtSize": {
                        "type": "string",
                        "nullable": true
                      },
                      "remarks": {
                        "type": "string",
                        "nullable": true
                      }
                    },
                    "type": "object"
                  },
                  "termsAccepted": {
                    "description": "Whether terms were accepted",
                    "type": "boolean"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Booking created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "booking": {
                      "$ref": "#/components/schemas/PublicBooking"
                    },
                    "customer": {
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "email": {
                          "type": "string"
                        },
                        "fullName": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "participant": {
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "fullName": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/bookings/series": {
      "post": {
        "tags": [
          "Public - Bookings"
        ],
        "summary": "Create a series booking",
        "description": "Create a booking for an entire series. This endpoint handles guest checkout by creating/finding customer and participant records automatically.",
        "operationId": "createPublicSeriesBooking",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "seriesId",
                  "customer",
                  "participant"
                ],
                "properties": {
                  "seriesId": {
                    "description": "Series to book",
                    "type": "string",
                    "format": "uuid"
                  },
                  "customer": {
                    "required": [
                      "email",
                      "firstName",
                      "lastName"
                    ],
                    "properties": {
                      "email": {
                        "type": "string",
                        "format": "email"
                      },
                      "firstName": {
                        "type": "string"
                      },
                      "lastName": {
                        "type": "string"
                      },
                      "phone": {
                        "type": "string",
                        "nullable": true
                      },
                      "nationalRegistryNumber": {
                        "type": "string",
                        "nullable": true
                      }
                    },
                    "type": "object"
                  },
                  "participant": {
                    "required": [
                      "firstName",
                      "lastName"
                    ],
                    "properties": {
                      "firstName": {
                        "type": "string"
                      },
                      "lastName": {
                        "type": "string"
                      },
                      "nationalRegistryNumber": {
                        "type": "string",
                        "nullable": true
                      },
                      "tshirtSize": {
                        "type": "string",
                        "nullable": true
                      },
                      "remarks": {
                        "type": "string",
                        "nullable": true
                      }
                    },
                    "type": "object"
                  },
                  "discountCode": {
                    "description": "Optional discount code",
                    "type": "string",
                    "nullable": true
                  },
                  "termsAccepted": {
                    "description": "Whether terms were accepted",
                    "type": "boolean"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Booking created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "booking": {
                      "$ref": "#/components/schemas/PublicBooking"
                    },
                    "customer": {
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "email": {
                          "type": "string"
                        },
                        "fullName": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "participant": {
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "fullName": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/bookings/{id}": {
      "get": {
        "tags": [
          "Public - Bookings"
        ],
        "summary": "Get booking details",
        "description": "Retrieve details of a booking including payment status and applied gift cards.",
        "operationId": "getPublicBooking",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "description": "Booking details",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "booking": {
                      "$ref": "#/components/schemas/PublicBooking"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "tags": [
          "Public - Bookings"
        ],
        "summary": "Abandon a pending booking",
        "description": "Cancel an unpaid (pending) booking and release the seat it holds. Confirmed bookings cannot be cancelled through this public endpoint (403); customers cancel them from their account.",
        "operationId": "cancelPublicBooking",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "description": "Booking cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "message": {
                      "type": "string",
                      "example": "Booking cancelled successfully"
                    },
                    "refund": {
                      "description": "Always false: only unpaid bookings can be cancelled here",
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "description": "Booking is confirmed; cancel it from the customer account"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/bookings/{id}/payment": {
      "post": {
        "tags": [
          "Public - Bookings"
        ],
        "summary": "Initiate payment",
        "description": "Start the payment process for a booking. Returns a checkout URL to redirect the user to the payment gateway.",
        "operationId": "initiatePublicPayment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "redirectUrl"
                ],
                "properties": {
                  "redirectUrl": {
                    "description": "URL to redirect after payment",
                    "type": "string",
                    "format": "uri",
                    "example": "https://example.com/booking/confirmation"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Payment initiated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "payment": {
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "status": {
                          "type": "string"
                        },
                        "amount": {
                          "$ref": "#/components/schemas/Money"
                        },
                        "checkoutUrl": {
                          "description": "Redirect user here to complete payment",
                          "type": "string",
                          "format": "uri"
                        },
                        "gatewayTransactionId": {
                          "type": "string"
                        },
                        "gatewayType": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/bookings/{id}/gift-cards": {
      "post": {
        "tags": [
          "Public - Bookings"
        ],
        "summary": "Apply gift cards",
        "description": "Apply one or more gift cards to a booking to reduce the payment amount.",
        "operationId": "applyGiftCardsToBooking",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "giftCardCodes"
                ],
                "properties": {
                  "giftCardCodes": {
                    "description": "Gift card codes to apply",
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "example": [
                      "TT-XXXX-XXXX",
                      "TT-YYYY-YYYY"
                    ]
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Gift cards applied",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "appliedGiftCards": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "giftCardId": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "code": {
                            "type": "string"
                          },
                          "amountRedeemed": {
                            "$ref": "#/components/schemas/Money"
                          },
                          "remainingBalance": {
                            "$ref": "#/components/schemas/Money"
                          }
                        },
                        "type": "object"
                      }
                    },
                    "totalGiftCardAmount": {
                      "$ref": "#/components/schemas/Money"
                    },
                    "remainingToPay": {
                      "$ref": "#/components/schemas/Money"
                    },
                    "fullyCovered": {
                      "description": "If true, no payment needed",
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/bookings/{id}/gift-cards/{code}": {
      "delete": {
        "tags": [
          "Public - Bookings"
        ],
        "summary": "Remove gift card",
        "description": "Remove a previously applied gift card from a booking.",
        "operationId": "removeGiftCardFromBooking",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          },
          {
            "name": "code",
            "in": "path",
            "description": "Gift card code to remove",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Gift card removed",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "removed": {
                      "description": "The removed gift card code",
                      "type": "string"
                    },
                    "amountRestored": {
                      "$ref": "#/components/schemas/Money"
                    },
                    "newRemainingToPay": {
                      "$ref": "#/components/schemas/Money"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/bookings/{id}/confirm": {
      "post": {
        "tags": [
          "Public - Bookings"
        ],
        "summary": "Confirm booking (gift card only)",
        "description": "Confirm a booking that is fully covered by gift cards. This endpoint only works when the booking has zero remaining payment due.",
        "operationId": "confirmPublicBooking",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "description": "Booking confirmed",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "booking": {
                      "$ref": "#/components/schemas/PublicBooking"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/bookings/{id}/calendar.ics": {
      "get": {
        "tags": [
          "Public - Calendar"
        ],
        "summary": "Download calendar file",
        "description": "Download an ICS calendar file for a booking to import into calendar applications.",
        "operationId": "downloadBookingCalendar",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "description": "ICS calendar file",
            "content": {
              "text/calendar": {}
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/bookings/{id}/calendar-links": {
      "get": {
        "tags": [
          "Public - Calendar"
        ],
        "summary": "Get calendar links",
        "description": "Get URLs for adding a booking to various calendar applications (Google Calendar, ICS download).",
        "operationId": "getBookingCalendarLinks",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "description": "Calendar links",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "googleCalendarUrl": {
                      "description": "URL to add event to Google Calendar (null for series bookings)",
                      "type": "string",
                      "format": "uri",
                      "nullable": true
                    },
                    "icsDownloadUrl": {
                      "description": "Relative URL to download ICS file",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/custom-fields": {
      "get": {
        "tags": [
          "Public - Booking form"
        ],
        "summary": "Custom fields of the booking form",
        "description": "The organisation's custom fields asked for customers and participants in the booking form (#801), in order. Booking questions per session or series are GET /booking-questions.",
        "operationId": "publicListCustomFields",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Fields",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "customFieldDefinitions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicFormCustomField"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/{org_slug}/booking-questions": {
      "get": {
        "tags": [
          "Public - Booking form"
        ],
        "summary": "Booking questions and consents",
        "description": "The questions and consents asked when booking a session or series (#395), or when requesting a venue hire of an activity (#794). Answer them in the checkout's `answers` (once per booking) or `participants[].answers` (`perParticipant`). 400 without sessionId, seriesId or activityId.",
        "operationId": "publicListBookingQuestions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "sessionId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "seriesId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "activityId",
            "in": "query",
            "description": "A venue hire request",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Questions",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "questions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicFormBookingQuestion"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "No item given",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/{org_slug}/discount-codes/validate": {
      "post": {
        "tags": [
          "Public - Discount Codes"
        ],
        "summary": "Validate discount code",
        "description": "Validate a discount code and calculate the discount amount for a given order.",
        "operationId": "validatePublicDiscountCode",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "code",
                  "bookingType",
                  "orderAmountValue",
                  "orderAmountCurrency"
                ],
                "properties": {
                  "code": {
                    "description": "Discount code to validate",
                    "type": "string"
                  },
                  "bookingType": {
                    "description": "Type of booking",
                    "type": "string",
                    "enum": [
                      "session",
                      "series"
                    ]
                  },
                  "orderAmountValue": {
                    "description": "Order amount in smallest currency unit",
                    "type": "integer"
                  },
                  "orderAmountCurrency": {
                    "description": "Currency code",
                    "type": "string",
                    "example": "EUR"
                  },
                  "customerId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiscountValidation"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/gift-cards/options": {
      "get": {
        "tags": [
          "Public - Gift Cards"
        ],
        "summary": "Get gift card purchase options",
        "description": "Retrieve available gift card denominations and configuration for an organisation.",
        "operationId": "getGiftCardOptions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Gift card options",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "giftCardOptions": {
                      "properties": {
                        "enabled": {
                          "type": "boolean"
                        },
                        "denominations": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Money"
                          }
                        },
                        "currency": {
                          "type": "string",
                          "example": "EUR"
                        },
                        "expiryMonths": {
                          "type": "integer",
                          "example": 12
                        },
                        "allowCustomAmount": {
                          "type": "boolean"
                        },
                        "minimumAmount": {
                          "type": "integer",
                          "nullable": true
                        },
                        "maximumAmount": {
                          "type": "integer",
                          "nullable": true
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/gift-cards/validate": {
      "post": {
        "tags": [
          "Public - Gift Cards"
        ],
        "summary": "Validate gift card code",
        "description": "Validate a gift card code and retrieve its current balance.",
        "operationId": "validateGiftCard",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "description": "Gift card code",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation result",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "valid": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": "string",
                      "nullable": true
                    },
                    "code": {
                      "description": "Why the code cannot be used (when valid is false)",
                      "type": "string",
                      "nullable": true,
                      "enum": [
                        "invalid_code",
                        "expired",
                        "depleted",
                        "not_active"
                      ]
                    },
                    "giftCard": {
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "currentBalance": {
                          "$ref": "#/components/schemas/Money"
                        },
                        "expiresAt": {
                          "type": "string",
                          "format": "date-time"
                        }
                      },
                      "type": "object",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/gift-cards/order": {
      "post": {
        "tags": [
          "Public - Gift Cards"
        ],
        "summary": "Create gift card order",
        "description": "Create a gift card order and initiate payment. Returns a checkout URL to complete payment.",
        "operationId": "createGiftCardOrder",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "customerEmail",
                  "items",
                  "returnUrl"
                ],
                "properties": {
                  "customerEmail": {
                    "type": "string",
                    "format": "email"
                  },
                  "items": {
                    "type": "array",
                    "items": {
                      "properties": {
                        "amount": {
                          "description": "Amount in smallest currency unit",
                          "type": "integer"
                        },
                        "recipientName": {
                          "type": "string"
                        },
                        "recipientEmail": {
                          "type": "string",
                          "format": "email"
                        },
                        "message": {
                          "type": "string",
                          "nullable": true
                        }
                      },
                      "type": "object"
                    }
                  },
                  "returnUrl": {
                    "type": "string",
                    "format": "uri"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Order created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GiftCardOrder"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/gift-cards/order/{order_id}": {
      "get": {
        "tags": [
          "Public - Gift Cards"
        ],
        "summary": "Get gift card order status",
        "description": "Retrieve the status and details of a gift card order. Gift card codes are included after payment is complete.",
        "operationId": "getGiftCardOrder",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "order_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Order details",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "order": {
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "paid",
                            "cancelled"
                          ]
                        },
                        "customerEmail": {
                          "type": "string",
                          "format": "email"
                        },
                        "totalAmount": {
                          "$ref": "#/components/schemas/Money"
                        },
                        "items": {
                          "type": "array",
                          "items": {
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "amount": {
                                "$ref": "#/components/schemas/Money"
                              },
                              "recipientName": {
                                "type": "string"
                              },
                              "recipientEmail": {
                                "type": "string",
                                "format": "email"
                              },
                              "message": {
                                "type": "string",
                                "nullable": true
                              },
                              "giftCard": {
                                "properties": {
                                  "code": {
                                    "type": "string"
                                  },
                                  "expiresAt": {
                                    "type": "string",
                                    "format": "date-time"
                                  }
                                },
                                "type": "object",
                                "nullable": true
                              }
                            },
                            "type": "object"
                          }
                        },
                        "createdAt": {
                          "type": "string",
                          "format": "date-time"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/hire-requests/{id}/form": {
      "get": {
        "tags": [
          "Public - Hire requests"
        ],
        "summary": "The venue hire request form",
        "description": "What the request form of a venue hire activity (a published headcount activity with booking style `request`, #794) asks: its locations with their day parts (hours per weekday) and extras, the ticket types, whether billing details and a phone number are required, the earliest date and the deposit rule. Texts in the request language. 404 `not_requestable` for any other activity.",
        "operationId": "publicHireRequestForm",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The form",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "activity": {
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        },
                        "description": {
                          "type": "string",
                          "nullable": true
                        }
                      },
                      "type": "object"
                    },
                    "headcount": {
                      "description": "Party rules: minimum/maximum seats, phone required",
                      "type": "object"
                    },
                    "ticketTypes": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "requiresBillingDetails": {
                      "type": "boolean"
                    },
                    "phoneRequired": {
                      "type": "boolean"
                    },
                    "locations": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "address": {
                            "type": "string"
                          },
                          "dayParts": {
                            "type": "array",
                            "items": {
                              "properties": {
                                "id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "name": {
                                  "type": "string"
                                },
                                "hours": {
                                  "description": "ISO weekday => {start, end} in local time",
                                  "type": "object"
                                }
                              },
                              "type": "object"
                            }
                          },
                          "extras": {
                            "type": "array",
                            "items": {
                              "type": "object"
                            }
                          }
                        },
                        "type": "object"
                      }
                    },
                    "timezone": {
                      "type": "string",
                      "example": "Europe/Brussels"
                    },
                    "earliestDate": {
                      "type": "string",
                      "format": "date"
                    },
                    "deposit": {
                      "description": "Deposit rule: percentage or fixed amount",
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "`not_requestable`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/{org_slug}/hire-requests/availability": {
      "get": {
        "tags": [
          "Public - Hire requests"
        ],
        "summary": "Can this day part be requested on this date?",
        "description": "For the request form: the hours of a day part on a date and whether it can still be requested (`available` false with `code: too_soon` within the activity's notice). `otherRequestPending` warns that another open request overlaps at that location; it does not stop a request. 404 `not_requestable`, 422 `unknown_day_part` for a day part the activity does not offer, 422 `day_part_closed` when it is closed that day.",
        "operationId": "publicHireRequestAvailability",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "activityId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "dayPartId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Availability",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "date": {
                      "type": "string",
                      "format": "date"
                    },
                    "available": {
                      "type": "boolean"
                    },
                    "code": {
                      "type": "string",
                      "example": "too_soon",
                      "nullable": true
                    },
                    "startsAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "endsAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "otherRequestPending": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "`not_requestable`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "`unknown_day_part`, `day_part_closed`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/{org_slug}/hire-requests": {
      "post": {
        "tags": [
          "Public - Hire requests"
        ],
        "summary": "Request a venue hire",
        "description": "Sends a venue hire request (#794): no payment, staff answer with a quote the customer accepts in their account. Account-first like the checkout: signed in (optional `Authorization: Bearer` customer token; 401 `unauthenticated` when invalid) or a `customer` contact that creates an unverified account or attaches to the existing one without showing or changing it. Billing details are saved on the account only when it is the customer's own. 422 codes: `validation_failed`, `not_requestable`, `unknown_day_part`, `day_part_closed`, `outside_day_part`, `too_soon`, party ticket codes, `billing_details_required`, `invalid_billing_details`, `answers_required`. Rate-limited per IP address.",
        "operationId": "publicCreateHireRequest",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "activityId",
                  "dayPartId",
                  "date",
                  "tickets"
                ],
                "properties": {
                  "activityId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "dayPartId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "date": {
                    "type": "string",
                    "format": "date"
                  },
                  "startTime": {
                    "description": "Within the day part; default its start",
                    "type": "string",
                    "example": "18:00"
                  },
                  "endTime": {
                    "type": "string",
                    "example": "23:00"
                  },
                  "tickets": {
                    "type": "array",
                    "items": {
                      "properties": {
                        "ticketTypeId": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "quantity": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "extras": {
                    "type": "array",
                    "items": {
                      "properties": {
                        "extraId": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "quantity": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "customer": {
                    "$ref": "#/components/schemas/PublicCheckoutContactInput"
                  },
                  "billingDetails": {
                    "description": "{companyName, vatNumber?, street, postcode, town, country}",
                    "type": "object"
                  },
                  "answers": {
                    "description": "Booking question name => answer",
                    "type": "object"
                  },
                  "remarks": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Requested",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "hire": {
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "reference": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string",
                          "example": "new"
                        },
                        "date": {
                          "type": "string",
                          "format": "date"
                        },
                        "startsAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "endsAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "partySize": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "otherRequestPending": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "`unauthenticated`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`space_conflict`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "Refused (see codes)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts (`rate_limited`, `retryAfter`, `Retry-After` header)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/{org_slug}/locations": {
      "get": {
        "tags": [
          "Public - Locations"
        ],
        "summary": "List locations",
        "description": "Retrieve all locations for an organisation.",
        "operationId": "listPublicLocations",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "List of locations",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "locations": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicLocation"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/locations/{id}": {
      "get": {
        "tags": [
          "Public - Locations"
        ],
        "summary": "Get location details",
        "description": "Retrieve details of a specific location.",
        "operationId": "getPublicLocation",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "description": "Location details",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "location": {
                      "$ref": "#/components/schemas/PublicLocation"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/manifest.webmanifest": {
      "get": {
        "tags": [
          "Public - Organisation"
        ],
        "summary": "Web app manifest of the booking site",
        "description": "The installable booking site (#525, #356): name, theme colour from the branding and icons (maskable ones included); the app opens on the customer's upcoming sessions. Cached an hour.",
        "operationId": "publicWebManifest",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The manifest",
            "content": {
              "application/manifest+json": {
                "schema": {
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "short_name": {
                      "type": "string"
                    },
                    "start_url": {
                      "type": "string"
                    },
                    "scope": {
                      "type": "string"
                    },
                    "display": {
                      "type": "string",
                      "example": "standalone"
                    },
                    "theme_color": {
                      "type": "string"
                    },
                    "background_color": {
                      "type": "string"
                    },
                    "icons": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/{org_slug}/organisation": {
      "get": {
        "tags": [
          "Public - Organisation"
        ],
        "summary": "Get organisation by slug",
        "description": "Retrieve public information about an organisation using its URL slug.",
        "operationId": "getPublicOrganisation",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Organisation details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicOrganisation"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/plans": {
      "get": {
        "tags": [
          "Public - Plans"
        ],
        "summary": "Passes and subscriptions on sale",
        "description": "Plans that can be bought now (#377), texts in the request language. Buying needs a verified customer: POST /api/customer/{org_slug}/plan-purchases.",
        "operationId": "publicListPlans",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Plans",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "plans": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicPlan"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/{org_slug}/plans/{id}": {
      "get": {
        "tags": [
          "Public - Plans"
        ],
        "summary": "A pass or subscription on sale",
        "description": "One plan that can be bought now (#698: the detail page is public). 404 `not_on_sale` when it cannot be bought now.",
        "operationId": "publicGetPlan",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Plan id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The plan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicPlan"
                }
              }
            }
          },
          "404": {
            "description": "`not_on_sale`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/{org_slug}/activities/{id}/reviews": {
      "get": {
        "tags": [
          "Public - Reviews"
        ],
        "summary": "Reviews of an activity",
        "description": "Published reviews of the activity, newest first, with the average rating and count (#355). When the organisation doesn't show reviews publicly the answer is only `{\"shown\": false}`. Authors are shown as first name and initial.",
        "operationId": "publicListActivityReviews",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 10,
              "maximum": 50,
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The reviews",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicReviewList"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/{org_slug}/review-requests/opt-out": {
      "post": {
        "tags": [
          "Public - Preferences"
        ],
        "summary": "Stop \"How was it?\" emails",
        "description": "The opt-out link of review request emails (#355): no more review requests for this customer. 404 when the customer id and token don't match.",
        "operationId": "publicOptOutOfReviewRequests",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "customerId",
                  "token"
                ],
                "properties": {
                  "customerId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "token": {
                    "description": "From the email's opt-out link",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Opted out"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/{org_slug}/booking-nudges/opt-out": {
      "post": {
        "tags": [
          "Public - Preferences"
        ],
        "summary": "Stop booking reminder emails",
        "description": "The opt-out link of \"you didn't finish your booking\" and \"spots left\" emails (#747): opts the customer out of these nudges. 404 when the customer id and token don't match.",
        "operationId": "publicOptOutOfBookingNudges",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "customerId",
                  "token"
                ],
                "properties": {
                  "customerId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "token": {
                    "description": "From the email's opt-out link",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Opted out"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/{org_slug}/series": {
      "get": {
        "tags": [
          "Public - Series"
        ],
        "summary": "List active series",
        "description": "Retrieve all active series for an organisation. Can be filtered by activity and/or location.",
        "operationId": "listPublicSeries",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "activityId",
            "in": "query",
            "description": "Filter by activity UUID",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "locationId",
            "in": "query",
            "description": "Filter by location UUID",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of series",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "series": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicSeries"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/series/{id}": {
      "get": {
        "tags": [
          "Public - Series"
        ],
        "summary": "One series",
        "description": "An active series of a published activity (#525), also when it cannot be booked right now, with the dates of its sessions still to come. 404 for an unknown series; 404 `not_available` when it is not on offer (#781).",
        "operationId": "publicGetSeries",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The series",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "series": {
                      "$ref": "#/components/schemas/PublicSeriesDetail"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or `not_available`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerErrorWithCode"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/{org_slug}/sessions/{id}": {
      "get": {
        "tags": [
          "Public - Sessions"
        ],
        "summary": "Get session details",
        "description": "Retrieve details of a specific session including availability.",
        "operationId": "getPublicSession",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "$ref": "#/components/parameters/ResourceId"
          }
        ],
        "responses": {
          "200": {
            "description": "Session details",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "session": {
                      "$ref": "#/components/schemas/PublicSession"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/sessions": {
      "get": {
        "tags": [
          "Public - Sessions"
        ],
        "summary": "List upcoming sessions (agenda)",
        "description": "Upcoming sessions of all published activities within a date range (at most 62 days, never before now), sorted by start time. `nextStart` is the first matching session after the range, so an empty day can point to the next one.",
        "operationId": "listPublicSessions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "from",
            "in": "query",
            "description": "Start of the range (ISO 8601); defaults to now",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "End of the range (ISO 8601); defaults to 14 days after `from`",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "age",
            "in": "query",
            "description": "The child's age in whole years; activities whose age range covers part of that year",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 100,
              "minimum": 0
            }
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "activityId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Upcoming sessions",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "sessions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicSession"
                      }
                    },
                    "dateRange": {
                      "properties": {
                        "start": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "end": {
                          "type": "string",
                          "format": "date-time"
                        }
                      },
                      "type": "object"
                    },
                    "hasMore": {
                      "description": "More sessions match than were returned",
                      "type": "boolean"
                    },
                    "nextStart": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid date or age"
          }
        }
      }
    },
    "/api/public/{org_slug}/locations/{location_id}/sessions": {
      "get": {
        "tags": [
          "Public - Sessions"
        ],
        "summary": "List sessions by location",
        "description": "Retrieve sessions for a specific location within a date range. Defaults to current week if dates not specified.",
        "operationId": "listPublicSessionsByLocation",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "location_id",
            "in": "path",
            "description": "Location UUID",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "start",
            "in": "query",
            "description": "Start date (ISO 8601 format)",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "example": "2024-01-15T00:00:00+00:00"
          },
          {
            "name": "end",
            "in": "query",
            "description": "End date (ISO 8601 format)",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "example": "2024-01-21T23:59:59+00:00"
          }
        ],
        "responses": {
          "200": {
            "description": "List of sessions",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "sessions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicSession"
                      }
                    },
                    "dateRange": {
                      "properties": {
                        "start": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "end": {
                          "type": "string",
                          "format": "date-time"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/activities/{id}/slot-offers": {
      "get": {
        "tags": [
          "Public - Activities"
        ],
        "summary": "Where and with whom a service is booked at a free moment",
        "description": "For an activity with bookingStyle \"slots\" (#924): its active offers per location, with the length of a moment, the seats per moment, the price (null: the activity's default price; ticketPrices for a headcount activity) and, when a moment takes a staff member, the staff members a customer can pick (first names only; members who opted out are left out but still take moments for \"anyone\").",
        "operationId": "listPublicSlotOffers",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Offers",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "offers": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "locationId": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "locationName": {
                            "type": "string"
                          },
                          "durationMinutes": {
                            "type": "integer"
                          },
                          "capacity": {
                            "type": "integer"
                          },
                          "price": {
                            "oneOf": [
                              {
                                "$ref": "#/components/schemas/Money"
                              }
                            ],
                            "nullable": true
                          },
                          "ticketPrices": {
                            "type": "object"
                          },
                          "resource": {
                            "type": "string",
                            "enum": [
                              "staff",
                              "space"
                            ]
                          },
                          "staff": {
                            "type": "array",
                            "items": {
                              "properties": {
                                "id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "firstName": {
                                  "type": "string"
                                }
                              },
                              "type": "object"
                            }
                          },
                          "providers": {
                            "description": "How many staff members (or spaces) can take a moment, also those not showing their name; with one there is nobody to choose (#962)",
                            "type": "integer"
                          }
                        },
                        "type": "object"
                      }
                    },
                    "timezone": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/{org_slug}/activities/{id}/slot-days": {
      "get": {
        "tags": [
          "Public - Activities"
        ],
        "summary": "The days with free moments",
        "description": "Per local date (the organisation's timezone) the number of free moments, from `from` (default today) to `to` (default from + 30 days; at most 31 days per call). With staffId only that staff member's moments, else anyone's. locationId is needed when the service is offered at more than one location.",
        "operationId": "listPublicSlotDays",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "staffId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Days",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "offerId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "timezone": {
                      "type": "string"
                    },
                    "from": {
                      "type": "string",
                      "format": "date"
                    },
                    "to": {
                      "type": "string",
                      "format": "date"
                    },
                    "days": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "date": {
                            "type": "string",
                            "format": "date"
                          },
                          "slots": {
                            "type": "integer"
                          },
                          "reason": {
                            "description": "Only on a day without free moments: why. A staff member's moments must lie within their availability and the location's opening hours (its day parts).",
                            "type": "string",
                            "enum": [
                              "past",
                              "too_far_ahead",
                              "closed",
                              "no_availability",
                              "outside_opening_hours",
                              "too_soon",
                              "fully_booked"
                            ]
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "location_required, not_provider or invalid_range"
          },
          "429": {
            "description": "rate_limited"
          }
        }
      }
    },
    "/api/public/{org_slug}/activities/{id}/slots": {
      "get": {
        "tags": [
          "Public - Activities"
        ],
        "summary": "The free moments of a day",
        "description": "The moments that can be booked on a local date, with the seats left and (for a staff offer) the staff members free then who show their name. Book one with POST /checkout and `slot: {slotOfferId, start, staffId?}`, `start` exactly as returned here.",
        "operationId": "listPublicSlots",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "staffId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Moments",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "offerId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "date": {
                      "type": "string",
                      "format": "date"
                    },
                    "timezone": {
                      "type": "string"
                    },
                    "slots": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "start": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "end": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "seatsLeft": {
                            "type": "integer"
                          },
                          "staff": {
                            "type": "array",
                            "items": {
                              "properties": {
                                "id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "firstName": {
                                  "type": "string"
                                }
                              },
                              "type": "object"
                            }
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "location_required, not_provider or invalid_date"
          },
          "429": {
            "description": "rate_limited"
          }
        }
      }
    },
    "/api/public/{org_slug}/tags": {
      "get": {
        "tags": [
          "Public - Tags"
        ],
        "summary": "List tags",
        "description": "Retrieve all tags for an organisation. Tags can be used to filter activities, sessions, or series.",
        "operationId": "listPublicTags",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "List of tags",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "tags": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicTag"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/{org_slug}/terms/current": {
      "get": {
        "tags": [
          "Public - Terms"
        ],
        "summary": "Get current terms and conditions",
        "description": "Retrieve the current terms and conditions for an organisation. Returns whether terms acceptance is required and the terms content.",
        "operationId": "getCurrentPublicTerms",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Terms and conditions",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "termsRequired": {
                      "type": "boolean"
                    },
                    "displayMode": {
                      "type": "string",
                      "nullable": true
                    },
                    "externalUrl": {
                      "type": "string",
                      "format": "uri",
                      "nullable": true
                    },
                    "terms": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/TermsAndConditions"
                        }
                      ],
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/public/widget-presets/{id}": {
      "get": {
        "tags": [
          "Widgets"
        ],
        "summary": "A widget preset, as the embed loads it",
        "description": "What `/embed/v1/plek.js` reads for `<div data-plek-widget=\"wgt_…\">`. No authentication and no credentials: any website may read it (CORS `*`). Cached for a minute and revalidated with the ETag `\"{id}-{version}\"` (304 on If-None-Match). `host` is the page's https origin, recorded at most hourly so staff see where the widget is used.",
        "operationId": "publicGetWidgetPreset",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^wgt_[0-9A-Za-z]{8}$"
            }
          },
          {
            "name": "host",
            "in": "query",
            "description": "The embedding page's origin",
            "required": false,
            "schema": {
              "type": "string",
              "example": "https://www.example.com"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The preset",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=60"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "id": {
                      "type": "string",
                      "example": "wgt_a1B2c3D4"
                    },
                    "organisation": {
                      "properties": {
                        "slug": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "flow": {
                      "$ref": "#/components/schemas/WidgetFlow"
                    },
                    "config": {
                      "$ref": "#/components/schemas/WidgetConfig"
                    },
                    "version": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "304": {
            "description": "Not modified"
          },
          "404": {
            "description": "Unknown or archived (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        }
      }
    },
    "/api/org/{organisation_id}/bookings/{id}/receipt.pdf": {
      "get": {
        "tags": [
          "Bookings"
        ],
        "summary": "Receipt of a booking (PDF)",
        "description": "A payment confirmation with VAT per rate, not an invoice (#891); available for a confirmed booking with a price (booking detail `receiptAvailable`). Needs `booking:read` and `payment:read`.",
        "operationId": "getStaffBookingReceipt",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The receipt",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/entitlements/plan-purchases/{id}/receipt.pdf": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "Receipt of a plan purchase (PDF)",
        "description": "A payment confirmation of a paid plan purchase (#891; not an invoice). Available when the holding says purchase.receiptAvailable; 404 otherwise. Needs payment:read and entitlement:read.",
        "operationId": "getStaffPlanPurchaseReceipt",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Plan purchase id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The receipt",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/reports/{report}": {
      "get": {
        "tags": [
          "Reports and analytics"
        ],
        "summary": "A report",
        "description": "Groupings per report (the first is the default): attendance activity|series|session; no-shows customer|activity; revenue activity|method|source|month|week (money received, #711); fees activity|month; utilisation activity|weekday|hour|slot; liabilities and coach-hours have none (coach-hours takes detail=1 for one row per coach and session). Days and times are the organisation's. Day totals and coach pay have their own routes. Needs report:read.",
        "operationId": "getReport",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "report",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "attendance",
                "no-shows",
                "liabilities",
                "coach-hours",
                "revenue",
                "fees",
                "utilisation"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "First day (YYYY-MM-DD); default 30 days before `to`. At most three years",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Last day, inclusive; default today",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "activityId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "coachId",
            "in": "query",
            "description": "A staff member's user id",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "format",
            "in": "query",
            "description": "json, or a csv/xlsx download of the same rows (decimal amounts, headers in the request language, audit-logged)",
            "required": false,
            "schema": {
              "type": "string",
              "default": "json",
              "enum": [
                "json",
                "csv",
                "xlsx"
              ]
            }
          },
          {
            "name": "groupBy",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "example": "activity"
            }
          },
          {
            "name": "detail",
            "in": "query",
            "description": "Coach hours: 1 for one row per coach and session",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The report (JSON), or the file for csv/xlsx",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReportResult"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid format, period, id or grouping",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Unknown report",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/reports/day-totals": {
      "get": {
        "tags": [
          "Reports and analytics"
        ],
        "summary": "Day totals (Z-report)",
        "description": "Money in and out per local day, payment method and staff member (#737), from the same sources as revenue: booking payments with refunds on their own dates, plans, gift card orders, fees and hire charges. Needs report:read and payment:read.",
        "operationId": "getDayTotalsReport",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "from",
            "in": "query",
            "description": "First day (YYYY-MM-DD); default 30 days before `to`. At most three years",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Last day, inclusive; default today",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "activityId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "coachId",
            "in": "query",
            "description": "A staff member's user id",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "format",
            "in": "query",
            "description": "json, or a csv/xlsx download of the same rows (decimal amounts, headers in the request language, audit-logged)",
            "required": false,
            "schema": {
              "type": "string",
              "default": "json",
              "enum": [
                "json",
                "csv",
                "xlsx"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The report (JSON), or the file for csv/xlsx",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReportResult"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid format, period, id or grouping",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/reports/coach-pay": {
      "get": {
        "tags": [
          "Reports and analytics"
        ],
        "summary": "Coach pay",
        "description": "The coach hours report's sessions times each coach's pay rate of that day (#741): per hour, per session and per attendee checked in; sessions without a rate are counted apart. detail=1 gives one row per coach and session. Needs report:read and coach_pay:read (Admin only by default).",
        "operationId": "getCoachPayReport",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "from",
            "in": "query",
            "description": "First day (YYYY-MM-DD); default 30 days before `to`. At most three years",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Last day, inclusive; default today",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "activityId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "coachId",
            "in": "query",
            "description": "A staff member's user id",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "format",
            "in": "query",
            "description": "json, or a csv/xlsx download of the same rows (decimal amounts, headers in the request language, audit-logged)",
            "required": false,
            "schema": {
              "type": "string",
              "default": "json",
              "enum": [
                "json",
                "csv",
                "xlsx"
              ]
            }
          },
          {
            "name": "detail",
            "in": "query",
            "description": "1 for one row per coach and session",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The report (JSON), or the file for csv/xlsx",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReportResult"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid format, period, id or grouping",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/reviews": {
      "get": {
        "tags": [
          "Reviews"
        ],
        "summary": "Reviews",
        "description": "Newest first, with the average rating and count of what matches. Customer id and email only with customer:read:all. Needs review:read.",
        "operationId": "listReviews",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "activityId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "published",
                "hidden"
              ]
            }
          },
          {
            "name": "rating",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 5,
              "minimum": 1
            }
          },
          {
            "name": "coachId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 25,
              "maximum": 100,
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of reviews",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "reviews": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ReviewAdminReview"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "summary": {
                      "properties": {
                        "average": {
                          "type": "number",
                          "nullable": true
                        },
                        "count": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Unknown status or rating",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/reviews/{id}/publish": {
      "post": {
        "tags": [
          "Reviews"
        ],
        "summary": "Publish a review",
        "description": "Shows it on the public reviews. Needs review:write.",
        "operationId": "publishReview",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Review id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The review",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "review": {
                      "$ref": "#/components/schemas/ReviewAdminReview"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/reviews/{id}/hide": {
      "post": {
        "tags": [
          "Reviews"
        ],
        "summary": "Hide a review",
        "description": "Takes it off the public reviews. Needs review:write.",
        "operationId": "hideReview",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Review id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The review",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "review": {
                      "$ref": "#/components/schemas/ReviewAdminReview"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/reviews/{id}/reply": {
      "put": {
        "tags": [
          "Reviews"
        ],
        "summary": "Reply to a review",
        "description": "The organisation's public answer; null removes it. Needs review:write.",
        "operationId": "replyToReview",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Review id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "reply"
                ],
                "properties": {
                  "reply": {
                    "type": "string",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The review",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "review": {
                      "$ref": "#/components/schemas/ReviewAdminReview"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "The reply is not text, or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/reviews/settings": {
      "get": {
        "tags": [
          "Reviews"
        ],
        "summary": "Review settings",
        "description": "Needs review:read.",
        "operationId": "getReviewSettings",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReviewAdminSettings"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Reviews"
        ],
        "summary": "Change the review settings",
        "description": "Fields left out stay as they are. Audit-logged (reviews.settings_changed). Needs review:write.",
        "operationId": "updateReviewSettings",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReviewAdminSettings"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReviewAdminSettings"
                }
              }
            }
          },
          "422": {
            "description": "Invalid value (term weeks 1 to 52, Google link https)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/permissions": {
      "get": {
        "tags": [
          "Team"
        ],
        "summary": "The permission catalogue",
        "description": "Every permission roles can be composed from, by group, with the scopes it takes. Needs role:read.",
        "operationId": "listPermissions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Permissions",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "groups": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TeamPermissionGroup"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/me": {
      "get": {
        "tags": [
          "Team"
        ],
        "summary": "My role and permissions here",
        "description": "The caller's membership, role, effective permissions and the locations they are limited to (null: every location). An API key answers with its role and no membership. Needs membership only.",
        "operationId": "getMyAccess",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "My access",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "membershipId": {
                      "type": "string",
                      "format": "uuid",
                      "nullable": true
                    },
                    "organisationId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "role": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/TeamRoleRef"
                        }
                      ],
                      "nullable": true
                    },
                    "permissions": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "example": "session:read:all"
                      }
                    },
                    "locations": {
                      "description": "Null: every location",
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/roles": {
      "get": {
        "tags": [
          "Team"
        ],
        "summary": "Roles",
        "description": "The organisation's roles with their permissions and member counts. Needs role:read.",
        "operationId": "listRoles",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Roles",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "roles": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TeamRole"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Team"
        ],
        "summary": "Create a role",
        "description": "A role composed of permissions; names are unique (409, type role_rule_violation). Unknown permissions are a 400. Needs role:write.",
        "operationId": "createRole",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TeamRoleInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "role": {
                      "$ref": "#/components/schemas/TeamRole"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "name or permissions missing or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Name taken",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamDomainError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/roles/{role_id}": {
      "get": {
        "tags": [
          "Team"
        ],
        "summary": "One role",
        "description": "Needs role:read.",
        "operationId": "getRole",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "role_id",
            "in": "path",
            "description": "Role id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The role",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "role": {
                      "$ref": "#/components/schemas/TeamRole"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Team"
        ],
        "summary": "Change a role",
        "description": "Replaces name, description and permissions; members holding it get them at once. The Admin role is locked (409, type role_rule_violation). Needs role:write.",
        "operationId": "updateRole",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "role_id",
            "in": "path",
            "description": "Role id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TeamRoleInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "role": {
                      "$ref": "#/components/schemas/TeamRole"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "name or permissions missing or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Admin role, or name taken",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamDomainError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Team"
        ],
        "summary": "Delete a role",
        "description": "Only a role no member or API key holds, never the Admin role (409, type role_rule_violation). Needs role:write.",
        "operationId": "deleteRole",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "role_id",
            "in": "path",
            "description": "Role id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "In use, or the Admin role",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamDomainError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/segment-messages/preview": {
      "post": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Who a segment message would reach",
        "description": "How many customers the segment reaches with this kind of message and the first 100 of them; `withoutConsent` (marketing without consent) and `unreachable` (no deliverable email) are left out (#612). Needs message:write:all.",
        "operationId": "previewSegmentMessage",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "segment",
                  "kind"
                ],
                "properties": {
                  "segment": {
                    "$ref": "#/components/schemas/CustomerSegment"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "service",
                      "marketing"
                    ]
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The audience",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "count": {
                      "type": "integer"
                    },
                    "withoutConsent": {
                      "type": "integer"
                    },
                    "unreachable": {
                      "type": "integer"
                    },
                    "recipients": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "email": {
                            "type": "string",
                            "format": "email"
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid segment or kind",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/segment-messages": {
      "get": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Segment messages sent",
        "description": "Newest first, with how many were sent and failed. Needs message:write:all.",
        "operationId": "listSegmentMessages",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Messages",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "messages": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SegmentMessage"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Email a segment of customers",
        "description": "Queued for the worker, which emails every customer in the segment (marketing: only those who consented, with an unsubscribe link). Needs message:write:all.",
        "operationId": "sendSegmentMessage",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "segment",
                  "kind",
                  "subject",
                  "body"
                ],
                "properties": {
                  "segment": {
                    "$ref": "#/components/schemas/CustomerSegment"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "service",
                      "marketing"
                    ]
                  },
                  "subject": {
                    "type": "string"
                  },
                  "body": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "$ref": "#/components/schemas/SegmentMessage"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid segment, kind, subject or body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Nobody in the segment can receive it (code no_recipients)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/public/{org_slug}/unsubscribe": {
      "post": {
        "tags": [
          "Public - Preferences"
        ],
        "summary": "Unsubscribe from marketing email",
        "description": "The unsubscribe link of marketing email (#612): withdraws the customer's marketing consent. The link carries the customer id and a signed token; 404 when they don't match.",
        "operationId": "publicUnsubscribeFromMarketing",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "customerId",
                  "token"
                ],
                "properties": {
                  "customerId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "token": {
                    "description": "From the email's unsubscribe link",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Unsubscribed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/org/{organisation_id}/series": {
      "get": {
        "tags": [
          "Series"
        ],
        "summary": "List series",
        "description": "Paginated. With the assigned scope only the series the member coaches (or coaches a session of); members limited to locations only see theirs. Needs series:read.",
        "operationId": "listSeries",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "tagId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100,
              "minimum": 1
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "createdAt",
              "enum": [
                "createdAt",
                "name",
                "activity",
                "location",
                "startDate",
                "capacity",
                "price",
                "status"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "description": "Default desc for createdAt, else asc",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Series",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "series": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/StaffSeries"
                          },
                          {
                            "properties": {
                              "bookingCount": {
                                "description": "A series with bookings cannot be deleted",
                                "type": "integer"
                              }
                            },
                            "type": "object"
                          }
                        ]
                      }
                    },
                    "pagination": {
                      "properties": {
                        "total": {
                          "type": "integer"
                        },
                        "per_page": {
                          "type": "integer"
                        },
                        "current_page": {
                          "type": "integer"
                        },
                        "total_pages": {
                          "type": "integer"
                        },
                        "has_more": {
                          "type": "boolean"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid tag id or sort key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Series"
        ],
        "summary": "Create a series",
        "description": "Created as a draft; generate its sessions with generate-sessions. Assigning coaches needs series:write:all (else 403). Needs series:write:all.",
        "operationId": "createSeries",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffSeriesInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "series": {
                      "$ref": "#/components/schemas/StaffSeries"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid field, invalid translations",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Location not found (outside the member's locations)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}": {
      "get": {
        "tags": [
          "Series"
        ],
        "summary": "One series",
        "description": "Needs series:read (assigned scope: only a series the member coaches).",
        "operationId": "getSeries",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The series",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "series": {
                      "$ref": "#/components/schemas/StaffSeries"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Series"
        ],
        "summary": "Update a series",
        "description": "Replaces name, description, prices, capacity and booking options. Changing coaches needs series:write:all (else 403). Needs series:write.",
        "operationId": "updateSeries",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffSeriesInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The series",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "series": {
                      "$ref": "#/components/schemas/StaffSeries"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Series"
        ],
        "summary": "Delete a series",
        "description": "Only without bookings: a series with bookings answers 409 series_has_bookings (cancel it instead). Needs series:write.",
        "operationId": "deleteSeries",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "series_has_bookings",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "example": "series_has_bookings"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/activate": {
      "post": {
        "tags": [
          "Series"
        ],
        "summary": "Activate (publish) a series",
        "description": "Makes a draft series active. Needs series:write.",
        "operationId": "activateSeries",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The series",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "series": {
                      "$ref": "#/components/schemas/StaffSeries"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Not possible in its current status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/cancel": {
      "post": {
        "tags": [
          "Series"
        ],
        "summary": "Cancel a series",
        "description": "Cancels the series and its sessions; optionally its bookings, refunded through the gateway or as account credit (credit needs payment:write, else 403). Needs series:write.",
        "operationId": "cancelSeries",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "cancelBookings": {
                    "type": "boolean",
                    "default": false
                  },
                  "refund": {
                    "type": "boolean",
                    "default": false
                  },
                  "notify": {
                    "description": "Email the attendees",
                    "type": "boolean",
                    "default": true
                  },
                  "credit": {
                    "description": "Give the refunded share back as account credit instead of a gateway refund",
                    "type": "boolean",
                    "default": false
                  },
                  "creditExpiresOn": {
                    "type": "string",
                    "format": "date",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The series",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "series": {
                      "$ref": "#/components/schemas/StaffSeries"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid (e.g. status, creditExpiresOn)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/complete": {
      "post": {
        "tags": [
          "Series"
        ],
        "summary": "Mark a series completed",
        "description": "Needs series:write.",
        "operationId": "completeSeries",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The series",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "series": {
                      "$ref": "#/components/schemas/StaffSeries"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Not possible in its current status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/generate-sessions": {
      "post": {
        "tags": [
          "Series"
        ],
        "summary": "Generate the sessions of a series",
        "description": "Creates sessions by a repeating pattern; dates in closure periods (unless skipClosures is false) and excludedDates are skipped. Sessions in a confirmed hire/block are created closed. Needs series:write.",
        "operationId": "generateSeriesSessions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SeriesGenerateSessionsInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "sessionCount": {
                      "type": "integer"
                    },
                    "sessions": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "startTime": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "endTime": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "type": "object"
                      }
                    },
                    "skippedDates": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SeriesSkippedDate"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "space_conflict (reason appointments_booked): a session would take a staff member or space of a booked appointment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SeriesSpaceConflict"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/generate-sessions/preview": {
      "post": {
        "tags": [
          "Series"
        ],
        "summary": "Preview generated sessions",
        "description": "What generate-sessions would create, with the skipped dates; creates nothing, sessionPrice optional. Needs series:write.",
        "operationId": "previewSeriesSessions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SeriesGenerateSessionsInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Preview",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "sessionCount": {
                      "type": "integer"
                    },
                    "sessions": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "date": {
                            "type": "string",
                            "format": "date"
                          },
                          "startTime": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "endTime": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "type": "object"
                      }
                    },
                    "skippedDates": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SeriesSkippedDate"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/tags": {
      "get": {
        "tags": [
          "Series"
        ],
        "summary": "Tags of a series",
        "description": "Needs series:read.",
        "operationId": "listSeriesTags",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tags",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "tags": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "slug": {
                            "type": "string"
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updatedAt": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Series"
        ],
        "summary": "Tag a series",
        "description": "Needs series:write.",
        "operationId": "addSeriesTag",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "tagId"
                ],
                "properties": {
                  "tagId": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tagged",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "tagId missing or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/tags/{tag_id}": {
      "delete": {
        "tags": [
          "Series"
        ],
        "summary": "Untag a series",
        "description": "Needs series:write.",
        "operationId": "removeSeriesTag",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "tag_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Untagged",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/sessions": {
      "get": {
        "tags": [
          "Series"
        ],
        "summary": "Sessions of a series",
        "description": "All sessions of the series; a member with the assigned session scope only sees the sessions they coach. Needs series:read.",
        "operationId": "listSeriesSessions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sessions",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "sessions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffSeriesSession"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/sessions/batch": {
      "patch": {
        "tags": [
          "Series"
        ],
        "summary": "Change all sessions of a series",
        "description": "Applies the same time, capacity, price or tag changes to the sessions of the series. Needs series:write and session:write:all (else 403).",
        "operationId": "batchUpdateSeriesSessions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "startTime": {
                    "description": "Local HH:MM, together with endTime",
                    "type": "string",
                    "example": "18:30"
                  },
                  "endTime": {
                    "type": "string",
                    "example": "19:30"
                  },
                  "timezone": {
                    "description": "IANA; default the organisation's",
                    "type": "string",
                    "nullable": true
                  },
                  "capacity": {
                    "description": "Ignored when the time changes too",
                    "type": "integer"
                  },
                  "price": {
                    "$ref": "#/components/schemas/SeriesMoneyInput"
                  },
                  "tagsToAdd": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "tagsToRemove": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "updatedCount": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid value (time, timezone, currency)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/spaces": {
      "put": {
        "tags": [
          "Series"
        ],
        "summary": "Spaces the series needs",
        "description": "Sets the spaces its sessions need; the sessions still to come follow. Needs series:write:all.",
        "operationId": "setSeriesSpaces",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "spaceIds"
                ],
                "properties": {
                  "spaceIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The series",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "series": {
                      "$ref": "#/components/schemas/StaffSeries"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "spaceIds must be a list of space ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "space_conflict: a session to come clashes with a hire, block or booked session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SeriesSpaceConflict"
                }
              }
            }
          },
          "422": {
            "description": "Unknown or inactive space",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/extend/preview": {
      "post": {
        "tags": [
          "Series"
        ],
        "summary": "Preview extending a series",
        "description": "The sessions extending would add (weeks: repeat the last week N more weeks; sessions: N more at its cadence), closure dates skipped (#742). Nothing is changed. Needs series:write:all.",
        "operationId": "previewSeriesExtension",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SeriesExtensionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sessions it would add",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "sessions": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "startTime": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "endTime": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "type": "object"
                      }
                    },
                    "skippedDates": {
                      "description": "Dates skipped for a closure period",
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SeriesTermSkippedDate"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "mode/count missing or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "space_conflict (reason appointments_booked): a new session would lie over a booked appointment of its coach or space",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/extend": {
      "post": {
        "tags": [
          "Series"
        ],
        "summary": "Extend a series",
        "description": "Adds the sessions of the preview (#742). Audit-logged (series.extended). Needs series:write:all.",
        "operationId": "extendSeries",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SeriesExtensionInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Sessions added",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "sessions": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "startTime": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "endTime": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "type": "object"
                      }
                    },
                    "skippedDates": {
                      "description": "Dates skipped for a closure period",
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SeriesTermSkippedDate"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "mode/count missing or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "space_conflict (reason appointments_booked): a new session would lie over a booked appointment of its coach or space",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/perks": {
      "get": {
        "tags": [
          "Series"
        ],
        "summary": "Perks of a series booking",
        "description": "What a confirmed booking of the series gives on top, e.g. free sessions of another activity during the term (#339). Needs series:read:all.",
        "operationId": "getSeriesPerks",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Perks",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "perks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SeriesSettingsPerk"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Series"
        ],
        "summary": "Set the perks of a series booking",
        "description": "Replaces the perks (#339): each another activity of the organisation, at most once, with an unlimited or limited rule (no credits). Needs series:write:all.",
        "operationId": "setSeriesPerks",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "perks": {
                    "type": "array",
                    "items": {
                      "properties": {
                        "activityId": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "rule": {
                          "$ref": "#/components/schemas/SeriesSettingsPerkRule"
                        }
                      },
                      "type": "object"
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Perks",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "perks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SeriesSettingsPerk"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid rule, activity not found or covered twice, or the series' own activity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/batch/cancel": {
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Cancel several sessions",
        "description": "Cancels the listed sessions with the same choices as one session. With the assigned scope only the caller's own sessions; the others come back as skipped. Needs session:write; giving credit also payment:write.",
        "operationId": "batchCancelSessions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "sessionIds"
                ],
                "allOf": [
                  {
                    "properties": {
                      "sessionIds": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    },
                    "type": "object"
                  },
                  {
                    "$ref": "#/components/schemas/SessionCancelInput"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was cancelled and what was skipped",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionBatchResult"
                }
              }
            }
          },
          "400": {
            "description": "sessionIds not a list of ids, reason too long, or an invalid credit expiry date",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "code forbidden: giving account credit needs payment:write",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/batch/update": {
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Edit several sessions",
        "description": "Changes coaches, location, time (shift by minutes or a new local start time) or capacity of the listed sessions. With the assigned scope only the caller's own sessions; the others come back as skipped (e.g. capacity_below_bookings, not_scheduled). Needs session:write; changing coaches needs session:write:all.",
        "operationId": "batchUpdateSessions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "sessionIds"
                ],
                "properties": {
                  "sessionIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "coachIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "nullable": true
                  },
                  "locationId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  },
                  "shiftMinutes": {
                    "description": "Move every session by this many minutes",
                    "type": "integer",
                    "nullable": true
                  },
                  "startTime": {
                    "description": "New local start time (HH:MM), duration kept",
                    "type": "string",
                    "example": "18:30",
                    "nullable": true
                  },
                  "capacity": {
                    "type": "integer",
                    "nullable": true
                  },
                  "notifyAttendees": {
                    "type": "boolean",
                    "default": false
                  },
                  "locale": {
                    "description": "Language of the emails (default: Accept-Language)",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was changed and what was skipped",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionBatchResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid ids or values",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Assigning coaches needs session:write:all",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Location not found (or out of the member's locations)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "List sessions",
        "description": "Sessions of the organisation, paginated. With the assigned scope only the sessions the caller coaches; members limited to locations only see theirs. Needs session:read.",
        "operationId": "listSessions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "startDate",
            "in": "query",
            "description": "ISO 8601; without endDate everything from then on",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "endDate",
            "in": "query",
            "description": "ISO 8601",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "seriesId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "activityId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "activityIds",
            "in": "query",
            "description": "Comma-separated activity ids",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "tagId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "scheduled",
                "completed",
                "cancelled"
              ]
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "date",
              "enum": [
                "date",
                "activity",
                "location",
                "series",
                "capacity",
                "price",
                "status"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100,
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of sessions",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "sessions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffSessionListItem"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/SessionPagination"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid filter id, date, status or sort",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Create a session",
        "description": "A single session, optionally in a series. Needs session:write:all.",
        "operationId": "createSession",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffSessionInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "session": {
                      "$ref": "#/components/schemas/StaffSession"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Assigning coaches needs session:write:all",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Location or series not found (or out of the member's locations)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "code space_conflict: a hire, block or booked appointment takes a space or coach it needs",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionSpaceConflict"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/me/schedule": {
      "get": {
        "tags": [
          "Team"
        ],
        "summary": "My schedule",
        "description": "The sessions the caller coaches, whatever their scope, paginated; only at locations within their reach. Without startDate from the start of today. Needs session:read.",
        "operationId": "getMySchedule",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "startDate",
            "in": "query",
            "description": "ISO 8601; default the start of today",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "endDate",
            "in": "query",
            "description": "ISO 8601",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "seriesId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "activityId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "tagId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "scheduled",
                "completed",
                "cancelled"
              ]
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100,
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of sessions",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "sessions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffSessionListItem"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/SessionPagination"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid id, date or status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "Get a session",
        "description": "With the assigned scope only a session the caller coaches. Needs session:read.",
        "operationId": "getSession",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The session",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "session": {
                      "$ref": "#/components/schemas/StaffSession"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Sessions"
        ],
        "summary": "Update a session",
        "description": "Time, place, capacity, price, coaches and spaces. With notifyAttendees the attendees are emailed about a move. Appointments (origin slot) can't be moved. Needs session:write (assigned: only sessions the caller coaches); changing coaches needs session:write:all.",
        "operationId": "updateSession",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffSessionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "session": {
                      "$ref": "#/components/schemas/StaffSession"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Changing coaches needs session:write:all",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "code space_conflict: a hire, block or booked appointment takes a space or coach it needs",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionSpaceConflict"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Sessions"
        ],
        "summary": "Delete a session",
        "description": "Only a session without bookings or incident reports; otherwise cancel it. Needs session:write.",
        "operationId": "deleteSession",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The session can't be deleted (e.g. it has bookings)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/cancel-impact": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "What cancelling a session would touch",
        "description": "Active bookings, and how many of them could be refunded. Needs session:write.",
        "operationId": "getSessionCancelImpact",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Counts",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "bookedCount": {
                      "type": "integer"
                    },
                    "refundableBookingCount": {
                      "description": "Refunding is only worth offering above 0",
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/cancel": {
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Cancel a session",
        "description": "Optionally cancels its bookings, refunds them (or gives account credit) and emails the attendees. Needs session:write; giving credit also payment:write.",
        "operationId": "cancelSession",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SessionCancelInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "session": {
                      "$ref": "#/components/schemas/StaffSession"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (e.g. already cancelled)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "code forbidden: giving account credit needs payment:write",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/complete": {
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Mark a session as completed",
        "description": "Needs session:write.",
        "operationId": "completeSession",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Completed",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "session": {
                      "$ref": "#/components/schemas/StaffSession"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "The session can't be completed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/tags": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "Tags of a session",
        "description": "Needs session:read.",
        "operationId": "listSessionTags",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tags",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "tags": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SessionTag"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid session id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Tag a session",
        "description": "Needs session:write.",
        "operationId": "addSessionTag",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "tagId"
                ],
                "properties": {
                  "tagId": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tagged",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "tagId missing, unknown or already on the session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/tags/{tag_id}": {
      "delete": {
        "tags": [
          "Sessions"
        ],
        "summary": "Remove a tag from a session",
        "description": "Needs session:write.",
        "operationId": "removeSessionTag",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "tag_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/bookings": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "Confirmed bookings of a session",
        "description": "Direct bookings of the session that are confirmed: participants and parties. Needs booking:read.",
        "operationId": "listSessionBookings",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bookings",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "bookings": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SessionBookingRow"
                      }
                    },
                    "count": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid session id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{session_id}/equipment": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "Equipment a session needs",
        "description": "With the assigned scope only sessions the caller coaches. Needs session:read.",
        "operationId": "listSessionEquipment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Equipment",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "equipment": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "equipmentId": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "equipmentName": {
                            "type": "string"
                          },
                          "quantityNeeded": {
                            "type": "integer"
                          },
                          "totalQuantity": {
                            "type": "integer"
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Assign equipment to a session",
        "description": "Needs session:write; with the assigned scope only sessions the caller coaches.",
        "operationId": "assignSessionEquipment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "equipmentId"
                ],
                "properties": {
                  "equipmentId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "quantityNeeded": {
                    "type": "integer",
                    "default": 1
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Assigned",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "sessionEquipment": {
                      "properties": {
                        "equipmentId": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "equipmentName": {
                          "type": "string",
                          "nullable": true
                        },
                        "quantityNeeded": {
                          "type": "integer"
                        },
                        "createdAt": {
                          "type": "string",
                          "format": "date-time"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "equipmentId is required, or the equipment can't be assigned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{session_id}/equipment/{equipment_id}": {
      "delete": {
        "tags": [
          "Sessions"
        ],
        "summary": "Remove equipment from a session",
        "description": "Needs session:write; with the assigned scope only sessions the caller coaches.",
        "operationId": "removeSessionEquipment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "equipment_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid id or not assigned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/roster": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "Attendee list of a session",
        "description": "Everyone coming (session and series bookings, confirmed or still held), with check-in state. No payment data; coaches see name, date of birth, internal notes and the custom fields visible to coaches, never contacts. With the assigned scope only sessions the caller coaches. Needs attendance:read.",
        "operationId": "getSessionRoster",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The roster",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "sessionId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "capacity": {
                      "type": "integer"
                    },
                    "usesSpots": {
                      "type": "boolean"
                    },
                    "attendees": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SessionRosterAttendee"
                      }
                    },
                    "count": {
                      "type": "integer"
                    },
                    "checkedInCount": {
                      "type": "integer"
                    },
                    "absentCount": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/check-ins": {
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Mark an attendee present or absent",
        "description": "A participant by participantId, or a party as a whole by its bookingId. Marking someone absent can charge a no-show fee. Needs attendance:write; with the assigned scope only sessions the caller coaches.",
        "operationId": "checkInSessionAttendee",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "participantId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "bookingId": {
                    "description": "A party booking, instead of participantId",
                    "type": "string",
                    "format": "uuid"
                  },
                  "status": {
                    "type": "string",
                    "default": "present",
                    "enum": [
                      "present",
                      "absent"
                    ]
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Marked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionCheckIn"
                }
              }
            }
          },
          "400": {
            "description": "participantId or bookingId is required, or an invalid status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Not an attendee of this session, or the mark can't be set",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/check-ins/scan": {
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Check in with a scanned QR code or typed code",
        "description": "201 for a new check-in, 200 when already checked in. Needs attendance:write; with the assigned scope only sessions the caller coaches.",
        "operationId": "checkInSessionWithCode",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "description": "The QR payload or the typed check-in code",
                    "type": "string",
                    "maxLength": 200
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already checked in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionCodeCheckIn"
                }
              }
            }
          },
          "201": {
            "description": "Checked in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionCodeCheckIn"
                }
              }
            }
          },
          "400": {
            "description": "code invalid_code: no code given",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "code invalid_code, other_organisation, other_session, unknown_code, cancelled or not_confirmed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/check-ins/remaining": {
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Mark everyone not marked yet as present",
        "description": "In one transaction. Undo per attendee with DELETE /check-ins/{participant_id} (parties by booking id). Needs attendance:write; with the assigned scope only sessions the caller coaches.",
        "operationId": "markRemainingSessionAttendeesPresent",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Who was marked",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "marked": {
                      "type": "integer"
                    },
                    "participantIds": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "format": "uuid"
                      }
                    },
                    "bookingIds": {
                      "description": "Parties",
                      "type": "array",
                      "items": {
                        "type": "string",
                        "format": "uuid"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/check-ins/{participant_id}": {
      "delete": {
        "tags": [
          "Sessions"
        ],
        "summary": "Clear an attendance mark",
        "description": "For a participant, or a party by its booking id. Needs attendance:write; with the assigned scope only sessions the caller coaches.",
        "operationId": "undoSessionCheckIn",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "participant_id",
            "in": "path",
            "description": "Participant id, or a party's booking id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Cleared"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/absences": {
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Report an absence of a series participant",
        "description": "Staff report that a participant of a series booking won't come, at any time, optionally with a make-up credit. Needs attendance:write; with the assigned scope only sessions the caller coaches.",
        "operationId": "reportSessionAbsence",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "participantId"
                ],
                "properties": {
                  "participantId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "reason": {
                    "type": "string",
                    "nullable": true
                  },
                  "makeUpCredit": {
                    "description": "Give a make-up credit; null follows the booking rules",
                    "type": "boolean",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Reported",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "absence": {
                      "$ref": "#/components/schemas/SessionAbsence"
                    },
                    "makeUpCredit": {
                      "description": "The make-up credit given",
                      "type": "object",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "participantId is required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "code disabled, no_booking, not_in_series, session_cancelled, too_late, credit_used or seat_taken",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/absences/{participant_id}": {
      "delete": {
        "tags": [
          "Sessions"
        ],
        "summary": "Withdraw a reported absence",
        "description": "Needs attendance:write; with the assigned scope only sessions the caller coaches.",
        "operationId": "withdrawSessionAbsence",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "participant_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Withdrawn"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "code credit_used (its make-up credit was used) or seat_taken",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/spots": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "Who sits where",
        "description": "The spot layout of a session with assigned spots, who sits on each spot and who has none yet. With the assigned scope only sessions the caller coaches. Needs attendance:read.",
        "operationId": "getSessionSpots",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The map",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionSpots"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Sessions"
        ],
        "summary": "Set a session's spot layout",
        "description": "Opt in to assigned spots (capacity then comes from the layout) or out (layoutId null), choose who picks the spot, and block spots for this session. Needs session:write:all.",
        "operationId": "setSessionSpots",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "layoutId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  },
                  "selection": {
                    "type": "string",
                    "default": "auto",
                    "enum": [
                      "auto",
                      "customer"
                    ]
                  },
                  "blockedSpotIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The map",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionSpots"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid layout, selection or blocked spots",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/spots/bookings/{booking_id}": {
      "put": {
        "tags": [
          "Sessions"
        ],
        "summary": "Move an attendee to another spot",
        "description": "Swaps with whoever sits there; spotId null clears the spot. With the assigned scope only sessions the caller coaches. Needs booking:write.",
        "operationId": "moveSessionAttendeeSpot",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "booking_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "spotId": {
                    "type": "string",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The map",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionSpots"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "code spot_taken",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Unknown spot or booking",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/spots": {
      "get": {
        "tags": [
          "Series"
        ],
        "summary": "Spot settings of a series",
        "description": "The spot layout of the series and which spots are taken in its sessions to come (#496). Needs series:read (assigned scope: series I coach).",
        "operationId": "getSeriesSpots",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Spots",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SeriesSettingsSpots"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Series"
        ],
        "summary": "Set the spot layout of a series",
        "description": "Opt in to spots with a layout of the series' location (#496), or layoutId null to opt out; the layout must have room for everyone booked. Needs series:write:all.",
        "operationId": "setSeriesSpots",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "layoutId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  },
                  "selection": {
                    "description": "Handed out automatically or picked by the customer",
                    "type": "string",
                    "default": "auto",
                    "enum": [
                      "auto",
                      "customer"
                    ]
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Spots",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SeriesSettingsSpots"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid selection, layout not found or of another location, too few spots for the bookings; code headcount_spots_not_supported for a headcount activity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/public/{org_slug}/sessions/{id}/spots": {
      "get": {
        "tags": [
          "Public - Sessions"
        ],
        "summary": "The spot map of a session",
        "description": "For a session with assigned spots (#496): every spot of the room with its status (free, taken or blocked), never who sits there. `map` is null when the session has no spot layout. With `selection: customer` the customer picks a free spot (checkout `participants[].spotId`).",
        "operationId": "publicGetSessionSpots",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Session id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The map",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "map": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/PublicSpotsMap"
                        }
                      ],
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/{org_slug}/series/{id}/spots": {
      "get": {
        "tags": [
          "Public - Series"
        ],
        "summary": "The spot map of a series",
        "description": "For a series with assigned spots (#496): a spot is free when it is free in every session still to come (status free or taken). A series booking gets the same spot in every session to come. `map` is null when the series has no spot layout.",
        "operationId": "publicGetSeriesSpots",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The map",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "map": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/PublicSpotsMap"
                        }
                      ],
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/walk-ins/options": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "How a walk-in could pay",
        "description": "The session price and the entitlements (passes, subscriptions, make-up credits, perks) that would pay for this participant. Needs booking:write; with the assigned scope only sessions the caller coaches.",
        "operationId": "getSessionWalkInOptions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "participantId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Options",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "price": {
                      "$ref": "#/components/schemas/SessionMoney"
                    },
                    "alreadyBooked": {
                      "type": "boolean"
                    },
                    "full": {
                      "type": "boolean"
                    },
                    "entitlements": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "sourceType": {
                            "type": "string"
                          },
                          "validUntil": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          },
                          "balance": {
                            "description": "Credits left before this use",
                            "type": "integer",
                            "nullable": true
                          },
                          "units": {
                            "description": "What this session costs",
                            "type": "integer",
                            "nullable": true
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Session or participant not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{id}/walk-ins": {
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Add a walk-in",
        "description": "Books the participant into the session at the door and checks them in. Needs booking:write; with the assigned scope only sessions the caller coaches.",
        "operationId": "addSessionWalkIn",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "participantId",
                  "payment"
                ],
                "properties": {
                  "participantId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "payment": {
                    "type": "string",
                    "enum": [
                      "on_site",
                      "entitlement",
                      "free"
                    ]
                  },
                  "entitlementId": {
                    "description": "With payment entitlement",
                    "type": "string",
                    "nullable": true
                  },
                  "ignoreAgeRules": {
                    "description": "Book although the participant is outside the activity's age range",
                    "type": "boolean",
                    "default": false
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Booked and checked in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionWalkIn"
                }
              }
            }
          },
          "400": {
            "description": "code invalid: participantId is required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "code already_booked, full, invalid, an age rule (age_too_young, age_too_old, age_unknown; send ignoreAgeRules to book anyway), entitlement_* (the entitlement can't pay), or another booking refusal",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/slot-offers": {
      "get": {
        "tags": [
          "Appointments"
        ],
        "summary": "Slot offers",
        "description": "How services booked at a free moment are offered per location (#924). Needs activity:read.",
        "operationId": "listSlotOffers",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "activityId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Offers",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "offers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SlotOffer"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Appointments"
        ],
        "summary": "Offer a service at a location",
        "description": "One offer per activity and location (422 when there is one). Staff ids are members, spaces at that location. Audit-logged (slot_offer.created). Needs activity:write.",
        "operationId": "createSlotOffer",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SlotOfferInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SlotOffer"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/slot-offers/{id}": {
      "get": {
        "tags": [
          "Appointments"
        ],
        "summary": "One slot offer",
        "description": "Needs activity:read.",
        "operationId": "getSlotOffer",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The offer",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SlotOffer"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Appointments"
        ],
        "summary": "Change a slot offer",
        "description": "Fields left out stay as they are. Appointments already booked keep their time and price. Needs activity:write.",
        "operationId": "updateSlotOffer",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SlotOfferInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SlotOffer"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Appointments"
        ],
        "summary": "Remove a slot offer",
        "description": "Only an offer never booked; once booked switch it off (active: false) instead: 409 {code: offer_in_use}. Needs activity:write.",
        "operationId": "deleteSlotOffer",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Removed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Booked before",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/slot-offers/{id}/slots": {
      "get": {
        "tags": [
          "Appointments"
        ],
        "summary": "Moments staff can book",
        "description": "The moments of a local date staff can book for a customer: opening hours, schedules, lead time and horizon don't apply, busy times do; outsideSchedule warns. Needs booking:write.",
        "operationId": "listStaffSlots",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "staffId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Moments",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "offerId": {
                      "type": "string"
                    },
                    "date": {
                      "type": "string",
                      "format": "date"
                    },
                    "timezone": {
                      "type": "string"
                    },
                    "slots": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffSlot"
                      }
                    },
                    "reason": {
                      "description": "When no moment lies within the usual rules (availability ∩ opening hours, closures, lead time): why, as in slot-days",
                      "type": "string",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "invalid_date or not_provider",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/bookings/appointment": {
      "post": {
        "tags": [
          "Appointments"
        ],
        "summary": "Book a moment for a customer",
        "description": "Staff book a moment of a slot offer for an existing customer (#924): participants, or tickets for a headcount service. Opening hours, schedules, lead time and horizon don't apply; never over busy time. Without staffId the first free staff member in provider order. A saved participant's `entitlementId` pays with a pass or subscription covering the service, like a session (#935). With confirm it is confirmed straight away (paid on site or later) instead of held for payment for reservationMinutes; the customer gets the confirmation email when it is confirmed now (confirm, or free) unless notify is false (#962). Needs booking:write.",
        "operationId": "bookAppointment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "slotOfferId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "start": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "staffId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  },
                  "customerId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "participants": {
                    "type": "array",
                    "items": {
                      "properties": {
                        "participantId": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "firstName": {
                          "type": "string"
                        },
                        "lastName": {
                          "type": "string"
                        },
                        "dateOfBirth": {
                          "type": "string",
                          "format": "date"
                        },
                        "entitlementId": {
                          "description": "A saved participant's seat paid with the customer's pass, subscription or credit covering the service (#935): booked free and confirmed",
                          "type": "string",
                          "format": "uuid"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "tickets": {
                    "type": "array",
                    "items": {
                      "properties": {
                        "ticketTypeId": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "quantity": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "contact": {
                    "type": "object"
                  },
                  "reservationMinutes": {
                    "type": "integer",
                    "default": 60
                  },
                  "confirm": {
                    "type": "boolean",
                    "default": false
                  },
                  "notify": {
                    "type": "boolean",
                    "default": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Booked",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "checkoutId": {
                      "type": "string"
                    },
                    "bookings": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "sessionId": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "status": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "slot_taken, too_many_holds, not_available, unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "not_provider, invalid_slot, validation errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/activities/{id}/providers": {
      "get": {
        "tags": [
          "Appointments"
        ],
        "summary": "Who provides a service",
        "description": "The staff members who provide it, in the order \"anyone\" tries them (#930). Needs activity:read.",
        "operationId": "listProviders",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Providers",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "userIds": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "format": "uuid"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Appointments"
        ],
        "summary": "Set who provides a service",
        "description": "Replaces the providers, in order: an \"anyone\" booking takes the first one free then, then by id (#930). Members of the organisation only. Appointments already booked keep their staff member. Audit-logged (activity.providers_changed). Needs activity:write.",
        "operationId": "setProviders",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "userIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Providers",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "userIds": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "format": "uuid"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Unknown staff member",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/coaches/{user_id}/services": {
      "get": {
        "tags": [
          "Appointments"
        ],
        "summary": "Services a staff member provides",
        "description": "With whether their first name stays off the booking site. Needs activity:read.",
        "operationId": "listStaffServices",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Services",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "activityIds": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "format": "uuid"
                      }
                    },
                    "hidePublicName": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Appointments"
        ],
        "summary": "Set the services a staff member provides",
        "description": "A service kept keeps their place among its providers; a new one puts them last (#930). Needs activity:write.",
        "operationId": "setStaffServices",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "activityIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "hidePublicName": {
                    "type": "boolean"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Services",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "activityIds": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "format": "uuid"
                      }
                    },
                    "hidePublicName": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sms": {
      "get": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Text message settings",
        "description": "The Twilio connection (never the auth token), which messages also go by SMS, their wording per language and the webhook URLs to set on the Twilio number (#354). Needs sms:read.",
        "operationId": "getSmsSettings",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmsSettings"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Change the text message settings",
        "description": "Replaces the settings (custom wording is kept). Needs sms:write.",
        "operationId": "saveSmsSettings",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SmsSettingsInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmsSettings"
                }
              }
            }
          },
          "400": {
            "description": "Invalid value (Account SID, sender, daily limit, country)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sms/templates/{message}/{language}": {
      "put": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Change a text message's wording",
        "description": "At most 480 characters, with the message's {{ placeholders }} only; never participant or medical details. Needs sms:write.",
        "operationId": "saveSmsTemplate",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "message",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "session_reminder",
                "session_cancelled",
                "waitlist_spot",
                "appointment_moved"
              ]
            }
          },
          {
            "name": "language",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "en",
                "nl",
                "fr",
                "de"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "text"
                ],
                "properties": {
                  "text": {
                    "type": "string",
                    "maxLength": 480
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmsSettings"
                }
              }
            }
          },
          "400": {
            "description": "Empty, too long, unknown placeholder, or unknown message or language",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Back to the default text message wording",
        "description": "Needs sms:write.",
        "operationId": "resetSmsTemplate",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "message",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "session_reminder",
                "session_cancelled",
                "waitlist_spot",
                "appointment_moved"
              ]
            }
          },
          {
            "name": "language",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "en",
                "nl",
                "fr",
                "de"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Reset",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmsSettings"
                }
              }
            }
          },
          "400": {
            "description": "Unknown message or language",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sms/test": {
      "post": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Send a test text message",
        "description": "Sends a short test message to a number (not a customer) right away; it counts towards the limits and shows in the log. Needs sms:write.",
        "operationId": "sendTestSms",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "to"
                ],
                "properties": {
                  "to": {
                    "description": "A phone number; without a country code the default country is assumed",
                    "type": "string",
                    "example": "+32470123456"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The message as logged (null when it could not be queued)",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "sms": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/SentSms"
                        }
                      ],
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Not a valid phone number (code invalid_phone)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sms/log": {
      "get": {
        "tags": [
          "Email and text messages"
        ],
        "summary": "Text messages sent",
        "description": "Newest first, with their delivery status. Needs sms:read.",
        "operationId": "listSentSms",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "queued",
                "sent",
                "delivered",
                "undelivered",
                "failed",
                "rate_limited"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 25,
              "maximum": 100,
              "minimum": 1
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Messages",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "messages": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SentSms"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/locations/{location_id}/spaces": {
      "get": {
        "tags": [
          "Locations"
        ],
        "summary": "Spaces of a location",
        "description": "Rooms and combinations of rooms (#792), also switched-off ones. Needs location:read.",
        "operationId": "listSpaces",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "location_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Spaces",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "spaces": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffSpace"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Locations"
        ],
        "summary": "Add a space",
        "description": "A plain room, or a combination when partIds lists plain spaces of this location. hirePricing sets its venue hire fees (#794). Audit-logged. Needs location:write.",
        "operationId": "createSpace",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "location_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffSpaceInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffSpace"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid (no name, guests out of range, unknown parts or day parts, translations)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/locations/{location_id}/spaces/{id}": {
      "put": {
        "tags": [
          "Locations"
        ],
        "summary": "Change a space",
        "description": "The whole space again; hirePricing and translations left out stay as they are. Audit-logged. Needs location:write.",
        "operationId": "updateSpace",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "location_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "id",
            "in": "path",
            "description": "Space id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffSpaceInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffSpace"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Locations"
        ],
        "summary": "Remove a space",
        "description": "Only a space nothing uses: 409 {code: space_in_use} while sessions, series, hires, blocks or combinations use it (switch it off with active: false instead). Audit-logged. Needs location:write.",
        "operationId": "deleteSpace",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "location_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "id",
            "in": "path",
            "description": "Space id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Removed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "In use (code space_in_use)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/locations/{location_id}/day-parts": {
      "get": {
        "tags": [
          "Locations"
        ],
        "summary": "Day parts of a location",
        "description": "Named parts of the day with their hours per weekday (#792). Needs location:read.",
        "operationId": "listDayParts",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "location_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Day parts",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "dayParts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffDayPart"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Locations"
        ],
        "summary": "Add a day part",
        "description": "Hours per ISO weekday in the organisation's timezone. Audit-logged. Needs location:write.",
        "operationId": "createDayPart",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "location_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffDayPartInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffDayPart"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid (no name, bad weekday or times)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/locations/{location_id}/day-parts/{id}": {
      "put": {
        "tags": [
          "Locations"
        ],
        "summary": "Change a day part",
        "description": "The whole day part again; translations left out stay as they are. Audit-logged. Needs location:write.",
        "operationId": "updateDayPart",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "location_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "id",
            "in": "path",
            "description": "Day part id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffDayPartInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffDayPart"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Locations"
        ],
        "summary": "Remove a day part",
        "description": "Audit-logged. Needs location:write.",
        "operationId": "deleteDayPart",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "location_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "id",
            "in": "path",
            "description": "Day part id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Removed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/locations/{location_id}/occupancy": {
      "get": {
        "tags": [
          "Locations"
        ],
        "summary": "Day view of the spaces",
        "description": "Who uses which space on a local day (#792): the location's spaces, that day's day parts, the scheduled sessions with the spaces they need, and the active hires and staff blocks. Needs session:read:all.",
        "operationId": "getLocationOccupancy",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "location_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "date",
            "in": "query",
            "description": "Local date, default today",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The day",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "date": {
                      "type": "string",
                      "format": "date"
                    },
                    "timezone": {
                      "type": "string",
                      "example": "Europe/Brussels"
                    },
                    "from": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "to": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "spaces": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffSpace"
                      }
                    },
                    "dayParts": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "startsAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "endsAt": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "type": "object"
                      }
                    },
                    "sessions": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "activityId": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "activityName": {
                            "type": "string",
                            "nullable": true
                          },
                          "startsAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "endsAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "spaceIds": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "format": "uuid"
                            }
                          },
                          "capacity": {
                            "type": "integer"
                          },
                          "bookedCount": {
                            "type": "integer"
                          },
                          "closed": {
                            "description": "Closed by a hire or block",
                            "type": "boolean"
                          },
                          "closedByOccupationId": {
                            "type": "string",
                            "format": "uuid",
                            "nullable": true
                          }
                        },
                        "type": "object"
                      }
                    },
                    "occupations": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/StaffSpaceOccupation"
                          },
                          {
                            "properties": {
                              "spaceName": {
                                "type": "string",
                                "nullable": true
                              },
                              "hire": {
                                "description": "For a venue hire (#794)",
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "referenceCode": {
                                    "type": "string"
                                  },
                                  "status": {
                                    "type": "string"
                                  },
                                  "activityName": {
                                    "type": "string",
                                    "nullable": true
                                  },
                                  "contactName": {
                                    "type": "string",
                                    "nullable": true
                                  },
                                  "partySize": {
                                    "type": "integer"
                                  },
                                  "party": {
                                    "description": "Tickets, e.g. \"2 × Adult, 1 × Child\"",
                                    "type": "string"
                                  }
                                },
                                "type": "object",
                                "nullable": true
                              }
                            },
                            "type": "object"
                          }
                        ]
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "date is not YYYY-MM-DD",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/locations/{location_id}/blocks": {
      "post": {
        "tags": [
          "Locations"
        ],
        "summary": "Block a space",
        "description": "Takes the space (and the spaces it shares) for a period and closes the sessions sharing it. 409 {code: space_conflict, reason, conflicts} when a confirmed hire or block overlaps (space_occupied) or a shared session has bookings (sessions_have_bookings) or a booked appointment is in the way. Audit-logged. Needs session:write:all.",
        "operationId": "createSpaceBlock",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "location_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffSpaceBlockInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Blocked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffSpaceBlock"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Something is in the way",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionSpaceConflict"
                }
              }
            }
          },
          "422": {
            "description": "Unknown space or invalid times",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/locations/{location_id}/blocks/{id}": {
      "put": {
        "tags": [
          "Locations"
        ],
        "summary": "Move a block",
        "description": "New times (and note) for a staff block; sessions no longer covered reopen, newly covered ones close. Same 409 space_conflict as creating. Audit-logged. Needs session:write:all.",
        "operationId": "updateSpaceBlock",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "location_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "id",
            "in": "path",
            "description": "Block id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffSpaceBlockInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Moved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffSpaceBlock"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Something is in the way",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionSpaceConflict"
                }
              }
            }
          },
          "422": {
            "description": "Invalid times",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Locations"
        ],
        "summary": "Lift a block",
        "description": "Releases the block and reopens the sessions it closed, unless another hire or block still covers them. Audit-logged. Needs session:write:all.",
        "operationId": "deleteSpaceBlock",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "location_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "id",
            "in": "path",
            "description": "Block id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lifted",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "reopenedSessionIds": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "format": "uuid"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/locations/{location_id}/spot-layouts": {
      "get": {
        "tags": [
          "Locations"
        ],
        "summary": "Spot layouts of a location",
        "description": "Rooms with named spots on a grid (#496). Needs location:read.",
        "operationId": "listSpotLayouts",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "location_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Layouts",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "layouts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffSpotLayout"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Locations"
        ],
        "summary": "Add a spot layout",
        "description": "Spots need a unique label and a free cell inside the grid. Needs location:write.",
        "operationId": "createSpotLayout",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "location_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffSpotLayoutInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffSpotLayout"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid layout",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/locations/{location_id}/spot-layouts/{id}": {
      "get": {
        "tags": [
          "Locations"
        ],
        "summary": "One spot layout",
        "description": "Needs location:read.",
        "operationId": "getSpotLayout",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "location_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "id",
            "in": "path",
            "description": "Layout id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The layout",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffSpotLayout"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Locations"
        ],
        "summary": "Change a spot layout",
        "description": "The whole layout again; keep a spot's id to keep it. Spots with upcoming bookings can't be removed (422). Needs location:write.",
        "operationId": "updateSpotLayout",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "location_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "id",
            "in": "path",
            "description": "Layout id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffSpotLayoutInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffSpotLayout"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Invalid layout, or a removed spot has upcoming bookings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Locations"
        ],
        "summary": "Remove a spot layout",
        "description": "409 while sessions or series use it. Needs location:write.",
        "operationId": "deleteSpotLayout",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "location_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "id",
            "in": "path",
            "description": "Layout id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Removed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "In use",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/invitations": {
      "get": {
        "tags": [
          "Team"
        ],
        "summary": "Open invitations",
        "description": "Invitations not yet accepted or revoked, newest first; expired ones are marked. Needs member:read:all.",
        "operationId": "listInvitations",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Invitations",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "invitations": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TeamInvitation"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/invitations/{invitation_id}/resend": {
      "post": {
        "tags": [
          "Team"
        ],
        "summary": "Send an invitation again",
        "description": "Emails a fresh link and extends its expiry. 410 type invitation_closed when it was accepted or revoked. Needs member:write.",
        "operationId": "resendInvitation",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "invitation_id",
            "in": "path",
            "description": "Invitation id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resent",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "invitation": {
                      "$ref": "#/components/schemas/TeamInvitation"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamDomainError"
                }
              }
            }
          },
          "410": {
            "description": "No longer open",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamDomainError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/invitations/{invitation_id}": {
      "delete": {
        "tags": [
          "Team"
        ],
        "summary": "Revoke an invitation",
        "description": "Its link stops working. Needs member:write.",
        "operationId": "revokeInvitation",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "invitation_id",
            "in": "path",
            "description": "Invitation id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Revoked"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamDomainError"
                }
              }
            }
          },
          "410": {
            "description": "No longer open",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamDomainError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/me/notifications": {
      "get": {
        "tags": [
          "Team"
        ],
        "summary": "My notification emails",
        "description": "Which staff notification emails the caller gets for this organisation (#735): per category off, instant or a daily digest. Without stored preferences the role's defaults apply. Needs membership only.",
        "operationId": "getMyNotificationSettings",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamNotificationSettings"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Team"
        ],
        "summary": "Choose my notification emails",
        "description": "preferences: category => off | instant | digest; null goes back to the role's defaults. Needs membership only.",
        "operationId": "saveMyNotificationSettings",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "preferences"
                ],
                "properties": {
                  "preferences": {
                    "type": "object",
                    "nullable": true,
                    "additionalProperties": {
                      "type": "string",
                      "enum": [
                        "off",
                        "instant",
                        "digest"
                      ]
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamNotificationSettings"
                }
              }
            }
          },
          "422": {
            "description": "Invalid preferences",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/search": {
      "get": {
        "tags": [
          "Search"
        ],
        "summary": "Search customers, participants and bookings",
        "description": "A few of each for one term (#415). Each group follows the caller's own permission: no customers without customer:read, no bookings without booking:read, and with the assigned scope only what is linked to the caller; booking customer names only with customer:read. Needs participant:read.",
        "operationId": "staffSearch",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "q",
            "in": "query",
            "description": "The search term",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Per group",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 5,
              "maximum": 20,
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matches",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "query": {
                      "type": "string"
                    },
                    "customers": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "fullName": {
                            "type": "string"
                          },
                          "email": {
                            "type": "string",
                            "nullable": true
                          },
                          "phone": {
                            "type": "string",
                            "nullable": true
                          }
                        },
                        "type": "object"
                      }
                    },
                    "participants": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "fullName": {
                            "type": "string"
                          },
                          "dateOfBirth": {
                            "type": "string",
                            "format": "date",
                            "nullable": true
                          }
                        },
                        "type": "object"
                      }
                    },
                    "bookings": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "reference": {
                            "type": "string"
                          },
                          "participantName": {
                            "type": "string",
                            "nullable": true
                          },
                          "customerName": {
                            "type": "string",
                            "nullable": true
                          },
                          "activityName": {
                            "type": "string",
                            "nullable": true
                          },
                          "seriesName": {
                            "type": "string",
                            "nullable": true
                          },
                          "sessionStartTime": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          },
                          "status": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/tags": {
      "get": {
        "tags": [
          "Tags"
        ],
        "summary": "Tags",
        "description": "Tags for sessions and series, paginated (page, per_page). Needs tag:read.",
        "operationId": "listTags",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100,
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of tags",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "tags": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SessionTag"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/StaffPageMetadata"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Tags"
        ],
        "summary": "Create a tag",
        "description": "Needs tag:write.",
        "operationId": "createTag",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "name",
                  "slug"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 100
                  },
                  "slug": {
                    "description": "Unique within the organisation",
                    "type": "string",
                    "pattern": "^[a-z0-9-]+$",
                    "maxLength": 100
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "tag": {
                      "$ref": "#/components/schemas/SessionTag"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid name or slug, or the slug exists",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/tags/search": {
      "get": {
        "tags": [
          "Tags"
        ],
        "summary": "Find tags by name",
        "description": "For autocomplete; an empty q gives no tags. Needs tag:read.",
        "operationId": "searchTags",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "q",
            "in": "query",
            "description": "Part of the name",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching tags",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "tags": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SessionTag"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/tags/{id}": {
      "get": {
        "tags": [
          "Tags"
        ],
        "summary": "One tag",
        "description": "Needs tag:read.",
        "operationId": "getTag",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Tag id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The tag",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "tag": {
                      "$ref": "#/components/schemas/SessionTag"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Tags"
        ],
        "summary": "Change a tag",
        "description": "Needs tag:write.",
        "operationId": "updateTag",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Tag id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "name",
                  "slug"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 100
                  },
                  "slug": {
                    "description": "Unique within the organisation",
                    "type": "string",
                    "pattern": "^[a-z0-9-]+$",
                    "maxLength": 100
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "tag": {
                      "$ref": "#/components/schemas/SessionTag"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid name or slug, or the slug exists",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Tags"
        ],
        "summary": "Delete a tag",
        "description": "It comes off every session and series. Needs tag:write.",
        "operationId": "deleteTag",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Tag id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/tax-categories": {
      "get": {
        "tags": [
          "Tax categories"
        ],
        "summary": "Tax categories",
        "description": "The VAT rates of the organisation. Without any, no VAT is shown on receipts. Needs tax:read.",
        "operationId": "listTaxCategories",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Tax categories",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "taxCategories": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TaxCategory"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Tax categories"
        ],
        "summary": "Add a tax category",
        "description": "Rate in basis points (0..10000). isDefault makes it the default for prices without one. 400 for a missing name or invalid rate. Needs tax:write.",
        "operationId": "createTaxCategory",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaxCategoryInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "taxCategory": {
                      "$ref": "#/components/schemas/TaxCategory"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/tax-categories/{category_id}": {
      "get": {
        "tags": [
          "Tax categories"
        ],
        "summary": "One tax category",
        "description": "Needs tax:read.",
        "operationId": "getTaxCategory",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "category_id",
            "in": "path",
            "description": "Tax category id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The category",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "taxCategory": {
                      "$ref": "#/components/schemas/TaxCategory"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Tax categories"
        ],
        "summary": "Change a tax category",
        "description": "Prices and sales already made keep their rate snapshot. 400 for a missing name or invalid rate. Needs tax:write.",
        "operationId": "updateTaxCategory",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "category_id",
            "in": "path",
            "description": "Tax category id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaxCategoryInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "taxCategory": {
                      "$ref": "#/components/schemas/TaxCategory"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Tax categories"
        ],
        "summary": "Remove a tax category",
        "description": "Needs tax:write.",
        "operationId": "deleteTaxCategory",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "category_id",
            "in": "path",
            "description": "Tax category id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/tax-categories/{category_id}/set-default": {
      "post": {
        "tags": [
          "Tax categories"
        ],
        "summary": "Make a tax category the default",
        "description": "Answers all categories. Needs tax:write.",
        "operationId": "setDefaultTaxCategory",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "category_id",
            "in": "path",
            "description": "Tax category id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "All categories",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "taxCategories": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TaxCategory"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/api-keys/temporary": {
      "post": {
        "tags": [
          "API keys"
        ],
        "summary": "Get a one-hour test key for yourself",
        "description": "For the developer portal's \"Get a test key\". Any member, signed in to the staff app (never with an API key). The key acts for you: your role and locations, only the read permissions unless `scope` is `write` (refused with 403 `write_not_allowed` when your role can only read). It expires after an hour and shows under Settings > API & webhooks, where it can be revoked. At most 3 unexpired test keys per member (409 `too_many_temporary_keys`) and 10 requests an hour (429 `too_many_requests`). Audit-logged as `api_key.temporary_created`.",
        "operationId": "createTemporaryApiKey",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "scope"
                ],
                "properties": {
                  "scope": {
                    "type": "string",
                    "default": "read",
                    "enum": [
                      "read",
                      "write"
                    ]
                  },
                  "returnPath": {
                    "description": "A page of the developer portal to come back to, e.g. /reference/staff",
                    "type": "string",
                    "example": "/reference/staff",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The key; `secret` is in this response only",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "secret": {
                      "type": "string",
                      "example": "plek_live_3f9a..."
                    },
                    "apiKey": {
                      "$ref": "#/components/schemas/ApiKey"
                    },
                    "organisationId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "expiresAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "handOffUrl": {
                      "description": "The developer portal with the key in the fragment",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`write_not_allowed`, `not_member`, or an API key was used",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "`too_many_temporary_keys`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "`too_many_requests`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/copy/preview": {
      "post": {
        "tags": [
          "Series"
        ],
        "summary": "Preview copying series to a new term",
        "description": "The sessions each series would get in the new term (moved by whole weeks, closure periods skipped) and how many participants would get a re-enrolment offer (#736). Nothing is changed; a series that cannot be copied carries an `error`. Needs series:write:all.",
        "operationId": "previewSeriesTermCopy",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SeriesTermCopyInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Preview per series",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "series": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SeriesTermCopyPreview"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "seriesIds or termStart missing, invalid excludedDates or timezone",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/copy": {
      "post": {
        "tags": [
          "Series"
        ],
        "summary": "Copy series to a new term",
        "description": "Copies each series with its settings, prices, coaches, spots, policies, booking window, perks and tags; sessions move by whole weeks and closure dates are skipped. Optionally publishes the copies and re-enrols the participants of the source (a held seat each, emailed). Audit-logged (series.copied). Needs series:write:all.",
        "operationId": "copySeriesToTerm",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SeriesTermCopyInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The copies",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "series": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SeriesTermCopied"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input, or a series that cannot be copied to that term",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/reenrolment": {
      "get": {
        "tags": [
          "Series"
        ],
        "summary": "Re-enrolment overview of a series",
        "description": "Who of last term re-enrolled, is pending (seat held until), waits (no seat left), declined or lapsed, and how many booked without an offer (#736). Needs waitlist:read.",
        "operationId": "getSeriesReenrolment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Overview",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SeriesReenrolmentOverview"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/reenrolment/remind": {
      "post": {
        "tags": [
          "Series"
        ],
        "summary": "Email open re-enrolment offers again",
        "description": "Sends the re-enrolment offer email again to the pending offers (all, or the entryIds given). Audit-logged. Needs waitlist:write.",
        "operationId": "remindSeriesReenrolment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "entryIds": {
                    "description": "Only these offers; left out: every pending offer",
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "How many were reminded",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "reminded": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/reenrolment/{entry_id}/decline": {
      "post": {
        "tags": [
          "Series"
        ],
        "summary": "Decline a re-enrolment offer",
        "description": "The participant is not coming back: the held seat is passed on like any waiting list offer. Audit-logged. Needs waitlist:write.",
        "operationId": "declineSeriesReenrolment",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "entry_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The updated overview",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SeriesReenrolmentOverview"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "not_open: the offer is no longer open",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/terms": {
      "get": {
        "tags": [
          "Organisation settings"
        ],
        "summary": "Terms and conditions versions",
        "description": "Every version, drafts included. Needs terms:read.",
        "operationId": "listTermsVersions",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Versions",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OrgSettingsTermsVersion"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Organisation settings"
        ],
        "summary": "Draft a new terms version",
        "description": "A draft gets the next version number; customers only see it once published. Needs terms:write.",
        "operationId": "createTermsVersion",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "title",
                  "content"
                ],
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "content": {
                    "type": "string"
                  },
                  "summary": {
                    "type": "string",
                    "nullable": true
                  },
                  "effectiveFrom": {
                    "type": "string",
                    "format": "date-time",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Drafted",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OrgSettingsTermsVersion"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or value",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "title and content are required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/terms/current": {
      "get": {
        "tags": [
          "Organisation settings"
        ],
        "summary": "The active terms version",
        "description": "404 when none is published. Needs terms:read.",
        "operationId": "getCurrentTermsVersion",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "The active version",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OrgSettingsTermsVersion"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/terms/{id}": {
      "get": {
        "tags": [
          "Organisation settings"
        ],
        "summary": "One terms version",
        "description": "Needs terms:read.",
        "operationId": "getTermsVersion",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Terms version id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The version",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OrgSettingsTermsVersion"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Organisation settings"
        ],
        "summary": "Change a draft terms version",
        "description": "Only drafts (400 for a published version); fields left out stay as they are. Needs terms:write.",
        "operationId": "updateTermsVersion",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Terms version id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "content": {
                    "type": "string"
                  },
                  "summary": {
                    "type": "string",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OrgSettingsTermsVersion"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Published, invalid JSON or empty title/content",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Organisation settings"
        ],
        "summary": "Delete a draft terms version",
        "description": "Published versions stay (400). Needs terms:write.",
        "operationId": "deleteTermsVersion",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Terms version id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Published",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/terms/{id}/publish": {
      "post": {
        "tags": [
          "Organisation settings"
        ],
        "summary": "Publish a terms version",
        "description": "It becomes the active version customers accept; the previous one ends. Needs terms:write.",
        "operationId": "publishTermsVersion",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Terms version id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Published",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OrgSettingsTermsVersion"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Already published",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/terms/{id}/acceptances": {
      "get": {
        "tags": [
          "Organisation settings"
        ],
        "summary": "Who accepted a terms version",
        "description": "One acceptance per booking and participant. Needs terms:read.",
        "operationId": "listTermsAcceptances",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Terms version id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Acceptances",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "bookingId": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "participantId": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "customerId": {
                            "type": "string",
                            "format": "uuid",
                            "nullable": true
                          },
                          "acceptedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "ipAddress": {
                            "type": "string",
                            "nullable": true
                          }
                        },
                        "type": "object"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/activities/{id}/trial-offer": {
      "put": {
        "tags": [
          "Activities"
        ],
        "summary": "Set the trial class offer of an activity",
        "description": "A trial class (#402) of the series of this activity, unless a series sets its own; {\"offer\": null} removes it. Needs activity:write.",
        "operationId": "setActivityTrialOffer",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Activity id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "offer": {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/ActivityTrialOffer"
                      }
                    ],
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The offer",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "trialOffer": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/ActivityTrialOffer"
                        }
                      ],
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid offer",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Not for a headcount activity (code headcount_trial_not_supported)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{id}/trial-offer": {
      "put": {
        "tags": [
          "Series"
        ],
        "summary": "Set the trial offer of a series",
        "description": "A trial class of the series at a price (#402); offer null follows the activity, enabled false switches it off for this series. Needs series:write:all.",
        "operationId": "setSeriesTrialOffer",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "offer": {
                    "properties": {
                      "enabled": {
                        "type": "boolean"
                      },
                      "priceAmount": {
                        "description": "Cents, in the series' currency",
                        "type": "integer"
                      }
                    },
                    "type": "object",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The series' own offer",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "trialOffer": {
                      "properties": {
                        "enabled": {
                          "type": "boolean"
                        },
                        "priceAmount": {
                          "type": "integer"
                        }
                      },
                      "type": "object",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid offer",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "headcount_trial_not_supported: not for a headcount activity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/public/{org_slug}/series/{id}/trial": {
      "get": {
        "tags": [
          "Public - Series"
        ],
        "summary": "Trial class of a series",
        "description": "The trial offer of a series (#402: one session to try at this price; book it at checkout with `trial: true`) and its sessions still to come. `trialOffer` null and no sessions without an offer. Seat counts only when the organisation shows spots left.",
        "operationId": "publicGetSeriesTrial",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgSlug"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Series id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The offer",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "trialOffer": {
                      "properties": {
                        "price": {
                          "$ref": "#/components/schemas/PublicFormAmount"
                        }
                      },
                      "type": "object",
                      "nullable": true
                    },
                    "sessions": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "startTime": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "endTime": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "availableSpots": {
                            "type": "integer"
                          },
                          "isFull": {
                            "type": "boolean"
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/org/{organisation_id}/waitlist": {
      "get": {
        "tags": [
          "Waiting lists"
        ],
        "summary": "Everyone waiting",
        "description": "The waiting lists of sessions and series still to come with people in line, soonest first, paginated per list (#733). A member limited to locations sees theirs only. Customer name and email only with customer:read. counts covers every list matching the filters. Needs waitlist:read.",
        "operationId": "listWaitlists",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "session",
                "series"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "notified: offered a spot",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "waiting",
                "notified"
              ]
            }
          },
          {
            "name": "activityId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "seriesId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Page, from 1",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Items per page",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lists",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "lists": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffWaitlistList"
                      }
                    },
                    "counts": {
                      "$ref": "#/components/schemas/StaffWaitlistCounts"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/StaffPageMetadata"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/waitlist/counts": {
      "get": {
        "tags": [
          "Waiting lists"
        ],
        "summary": "People waiting",
        "description": "People in line on lists still to come, for the navigation badge (#733). Needs waitlist:read.",
        "operationId": "getWaitlistCounts",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Counts",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "counts": {
                      "$ref": "#/components/schemas/StaffWaitlistCounts"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/waitlist/notify-next": {
      "post": {
        "tags": [
          "Waiting lists"
        ],
        "summary": "Offer free spots on several lists",
        "description": "Offers the free spot of each list (1 to 50) to the next in line that fits; a list without a free spot or nobody waiting answers notified null. Errors (code): 404 unknown_list, 422 invalid_lists. Needs waitlist:write.",
        "operationId": "notifyNextOnWaitlists",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "lists"
                ],
                "properties": {
                  "lists": {
                    "type": "array",
                    "items": {
                      "required": [
                        "type",
                        "id"
                      ],
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "session",
                            "series"
                          ]
                        },
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        }
                      },
                      "type": "object"
                    },
                    "maxItems": 50,
                    "minItems": 1
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per list",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "type": {
                            "type": "string",
                            "enum": [
                              "session",
                              "series"
                            ]
                          },
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "notified": {
                            "oneOf": [
                              {
                                "$ref": "#/components/schemas/SessionWaitlistEntry"
                              }
                            ],
                            "nullable": true
                          },
                          "failed": {
                            "type": "boolean"
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "A list is not found (unknown_list)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          },
          "422": {
            "description": "Between 1 and 50 lists (invalid_lists)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffErrorWithCode"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/sessions/{session_id}/waitlist": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "Waiting list of a session",
        "description": "Everyone in line for the session (participants and parties), with their place and offer state. Needs waitlist:read.",
        "operationId": "listSessionWaitlist",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Entries",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "waitlistEntries": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SessionWaitlistEntry"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/series/{series_id}/waitlist": {
      "get": {
        "tags": [
          "Series"
        ],
        "summary": "Waiting list of a series",
        "description": "Every entry on the waiting list of a series (all statuses), including re-enrolment offers (origin reenrolment). Needs waitlist:read.",
        "operationId": "listSeriesWaitlist",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "series_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Entries",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "waitlistEntries": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SeriesWaitlistEntry"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "description": "Failed to retrieve waitlist entries",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          },
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/waitlist/{id}": {
      "get": {
        "tags": [
          "Waiting lists"
        ],
        "summary": "One waiting list entry",
        "description": "Needs waitlist:read.",
        "operationId": "getWaitlistEntry",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Waiting list entry id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The entry",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "waitlistEntry": {
                      "$ref": "#/components/schemas/SessionWaitlistEntry"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Waiting lists"
        ],
        "summary": "Take someone off a waiting list",
        "description": "Cancels the entry; a spot offered to it passes on. Needs waitlist:write.",
        "operationId": "removeWaitlistEntry",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Waiting list entry id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "waitlistEntry": {
                      "$ref": "#/components/schemas/SessionWaitlistEntry"
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Cannot be removed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/waitlist/{id}/notify": {
      "post": {
        "tags": [
          "Waiting lists"
        ],
        "summary": "Offer the free spot on this entry's list",
        "description": "Offers the free spot of the entry's session or series to the next in line that fits (not necessarily this entry). Without a free spot or anyone waiting: only a message. Needs waitlist:write.",
        "operationId": "notifyNextOnWaitlist",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Waiting list entry id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Notified, or nobody to notify",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "waitlistEntry": {
                      "$ref": "#/components/schemas/SessionWaitlistEntry"
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid entry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/widget-presets": {
      "get": {
        "tags": [
          "Widgets"
        ],
        "summary": "Widget presets",
        "description": "The active (not archived) presets, with where each was last seen embedded. Needs widget:read.",
        "operationId": "listWidgetPresets",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "responses": {
          "200": {
            "description": "Presets",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string",
                            "example": "wgt_a1B2c3D4"
                          },
                          "name": {
                            "type": "string"
                          },
                          "flow": {
                            "$ref": "#/components/schemas/WidgetFlow"
                          },
                          "updatedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "lastSeenOrigin": {
                            "description": "The https website it was last loaded on",
                            "type": "string",
                            "nullable": true
                          },
                          "lastSeenAt": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          }
                        },
                        "type": "object"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "Widgets"
        ],
        "summary": "Create a widget preset",
        "description": "At most 50 active presets per organisation. Audit-logged. Needs widget:write.",
        "operationId": "createWidgetPreset",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "name",
                  "flow"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "flow": {
                    "$ref": "#/components/schemas/WidgetFlow"
                  },
                  "config": {
                    "$ref": "#/components/schemas/WidgetConfig"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WidgetPreset"
                }
              }
            }
          },
          "422": {
            "description": "Invalid settings (code validation_failed, `errors` per field), unknown_reference or limit_reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WidgetPresetError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/widget-presets/{id}": {
      "get": {
        "tags": [
          "Widgets"
        ],
        "summary": "One widget preset",
        "description": "Needs widget:read.",
        "operationId": "getWidgetPreset",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Preset id (wgt_…)",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^wgt_[0-9A-Za-z]{8}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The preset",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WidgetPreset"
                }
              }
            }
          },
          "404": {
            "description": "Not found or archived (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WidgetPresetError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "Widgets"
        ],
        "summary": "Change a widget preset",
        "description": "Replaces name and config; send the `version` you read. The flow can't change. Snippets on websites pick it up within a minute. Audit-logged. Needs widget:write.",
        "operationId": "updateWidgetPreset",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Preset id (wgt_…)",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^wgt_[0-9A-Za-z]{8}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "name",
                  "config",
                  "version"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "config": {
                    "$ref": "#/components/schemas/WidgetConfig"
                  },
                  "version": {
                    "type": "integer"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WidgetPreset"
                }
              }
            }
          },
          "404": {
            "description": "Not found or archived (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WidgetPresetError"
                }
              }
            }
          },
          "409": {
            "description": "Changed in the meantime (code stale_version): reload and try again",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WidgetPresetError"
                }
              }
            }
          },
          "422": {
            "description": "Invalid settings (code validation_failed, `errors` per field) or unknown_reference",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WidgetPresetError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Widgets"
        ],
        "summary": "Delete a widget preset",
        "description": "Archives it: snippets using it stop showing it and the id is never reused. Audit-logged. Needs widget:write.",
        "operationId": "archiveWidgetPreset",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Preset id (wgt_…)",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^wgt_[0-9A-Za-z]{8}$"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Archived"
          },
          "404": {
            "description": "Not found or already archived (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WidgetPresetError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    },
    "/api/org/{organisation_id}/widget-presets/{id}/duplicate": {
      "post": {
        "tags": [
          "Widgets"
        ],
        "summary": "Copy a widget preset",
        "description": "A new preset with its own id and the same flow and settings. Needs widget:write.",
        "operationId": "duplicateWidgetPreset",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganisationId"
          },
          {
            "name": "id",
            "in": "path",
            "description": "Preset id (wgt_…)",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^wgt_[0-9A-Za-z]{8}$"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "name": {
                    "description": "Left out: a name derived from the original",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The copy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WidgetPreset"
                }
              }
            }
          },
          "404": {
            "description": "Not found or archived (code not_found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WidgetPresetError"
                }
              }
            }
          },
          "422": {
            "description": "Invalid name (code validation_failed) or limit_reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WidgetPresetError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "StaffPageMetadata": {
        "description": "Page-based pagination (query parameters page and per_page)",
        "properties": {
          "total": {
            "type": "integer"
          },
          "per_page": {
            "type": "integer"
          },
          "current_page": {
            "type": "integer"
          },
          "total_pages": {
            "type": "integer"
          },
          "has_more": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "StaffAmount": {
        "description": "An amount of money: cents and its currency",
        "properties": {
          "amount": {
            "description": "Smallest currency unit (cents)",
            "type": "integer",
            "example": 2500
          },
          "currency": {
            "type": "string",
            "example": "EUR"
          }
        },
        "type": "object"
      },
      "StaffActivityImage": {
        "properties": {
          "id": {
            "type": "string"
          },
          "path": {
            "description": "Storage path",
            "type": "string"
          },
          "order": {
            "type": "integer"
          },
          "altText": {
            "type": "string",
            "nullable": true
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        },
        "type": "object"
      },
      "ActivityFee": {
        "description": "A late-cancellation or no-show fee (#406): {type: fixed, amount} (cents) or {type: percentage, percentage}",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "fixed",
              "percentage"
            ]
          },
          "amount": {
            "description": "Cents, for type fixed",
            "type": "integer"
          },
          "percentage": {
            "description": "For type percentage",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "ActivityCancellationPolicy": {
        "description": "Cancellation policy (#391, #406)",
        "properties": {
          "freeCancellationHours": {
            "description": "Free cancellation until this many hours before the start",
            "type": "integer"
          },
          "lateCancellation": {
            "type": "string",
            "enum": [
              "not_allowed",
              "no_refund",
              "partial_refund"
            ]
          },
          "lateRefundPercentage": {
            "type": "integer"
          },
          "lateCancellationFee": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ActivityFee"
              }
            ],
            "nullable": true
          },
          "noShowFee": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ActivityFee"
              }
            ],
            "nullable": true
          },
          "entitlementBookings": {
            "description": "Pass/subscription bookings: lose the use (forfeit) or get it back and pay the fee",
            "type": "string",
            "enum": [
              "forfeit",
              "fee"
            ]
          }
        },
        "type": "object"
      },
      "ActivityTrialOffer": {
        "description": "Trial class offer (#402)",
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "priceAmount": {
            "description": "Cents, in the currency of the series/session price",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "ActivityHeadcountSettings": {
        "description": "Party rules of a headcount activity (#786)",
        "properties": {
          "minPartySeats": {
            "type": "integer"
          },
          "maxPartySeats": {
            "type": "integer",
            "nullable": true
          },
          "phoneRequired": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "ActivityHireSettings": {
        "description": "How a request (venue hire) activity is quoted (#794)",
        "properties": {
          "dayPartIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "deposit": {
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "percentage",
                  "fixed"
                ]
              },
              "percentage": {
                "type": "integer",
                "nullable": true
              },
              "amount": {
                "description": "Cents",
                "type": "integer",
                "nullable": true
              }
            },
            "type": "object"
          },
          "quoteValidityDays": {
            "type": "integer"
          },
          "balanceDueDays": {
            "type": "integer"
          },
          "belowMinimumSupplement": {
            "description": "Cents per person below the spaces' minimum",
            "type": "integer",
            "nullable": true
          },
          "taxCategoryId": {
            "type": "string",
            "nullable": true
          },
          "minNoticeDays": {
            "type": "integer",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffActivity": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "defaultPrice": {
            "properties": {
              "amount": {
                "description": "Cents",
                "type": "integer"
              },
              "currency": {
                "type": "string",
                "example": "EUR"
              },
              "formatted": {
                "type": "string",
                "example": "€12.00"
              }
            },
            "type": "object"
          },
          "images": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffActivityImage"
            }
          },
          "primaryImageId": {
            "type": "string",
            "nullable": true
          },
          "isPublished": {
            "type": "boolean"
          },
          "cancellationPolicy": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ActivityCancellationPolicy"
              }
            ],
            "nullable": true,
            "description": "The activity's own policy; null follows the organisation"
          },
          "trialOffer": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ActivityTrialOffer"
              }
            ],
            "nullable": true
          },
          "emailInfo": {
            "description": "\"Good to know\" notes (#704)",
            "type": "string",
            "nullable": true
          },
          "translations": {
            "description": "Other languages (#705): {\"fr\": {\"name\": \"…\", \"description\": \"…\", \"emailInfo\": \"…\"}}",
            "type": "object"
          },
          "minAgeMonths": {
            "type": "integer",
            "nullable": true
          },
          "maxAgeMonths": {
            "type": "integer",
            "nullable": true
          },
          "ageEnforcement": {
            "type": "string",
            "enum": [
              "info",
              "warn",
              "block"
            ]
          },
          "ageCutoff": {
            "description": "MM-DD cut-off date for the age, or null (age on the session date)",
            "type": "string",
            "nullable": true
          },
          "level": {
            "type": "string",
            "nullable": true
          },
          "attendeeMode": {
            "type": "string",
            "enum": [
              "participants",
              "headcount"
            ]
          },
          "bookingStyle": {
            "type": "string",
            "enum": [
              "instant",
              "request",
              "slots"
            ]
          },
          "headcount": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ActivityHeadcountSettings"
              }
            ],
            "nullable": true,
            "description": "Only for headcount activities"
          },
          "requiresBillingDetails": {
            "type": "boolean"
          },
          "productCode": {
            "type": "string",
            "nullable": true
          },
          "offeredExtraIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffActivityInput": {
        "required": [
          "name",
          "defaultPrice"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 255
          },
          "description": {
            "type": "string"
          },
          "defaultPrice": {
            "required": [
              "amount",
              "currency"
            ],
            "properties": {
              "amount": {
                "description": "Cents",
                "type": "integer"
              },
              "currency": {
                "type": "string",
                "example": "EUR",
                "maxLength": 3
              }
            },
            "type": "object"
          },
          "emailInfo": {
            "description": "Update only: \"Good to know\" notes",
            "type": "string",
            "nullable": true
          },
          "minAgeMonths": {
            "type": "integer",
            "nullable": true
          },
          "maxAgeMonths": {
            "type": "integer",
            "nullable": true
          },
          "level": {
            "type": "string",
            "nullable": true
          },
          "ageEnforcement": {
            "type": "string",
            "enum": [
              "info",
              "warn",
              "block"
            ]
          },
          "ageCutoff": {
            "type": "string",
            "example": "12-31",
            "nullable": true
          },
          "requiresBillingDetails": {
            "type": "boolean"
          },
          "productCode": {
            "description": "Left out: unchanged; null clears it",
            "type": "string",
            "nullable": true
          },
          "translations": {
            "description": "Left out: unchanged; {} clears them",
            "type": "object"
          }
        },
        "type": "object"
      },
      "StaffActivityResponse": {
        "properties": {
          "activity": {
            "$ref": "#/components/schemas/StaffActivity"
          }
        },
        "type": "object"
      },
      "StaffTicketType": {
        "description": "A ticket type of a headcount activity (#786)",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "price": {
            "$ref": "#/components/schemas/StaffAmount"
          },
          "seatWeight": {
            "description": "0: takes no seat (e.g. a baby on a lap)",
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "refundableOnLateCancel": {
            "type": "boolean"
          },
          "minPerBooking": {
            "type": "integer"
          },
          "maxPerBooking": {
            "type": "integer",
            "nullable": true
          },
          "productCode": {
            "type": "string",
            "nullable": true
          },
          "sortOrder": {
            "type": "integer"
          },
          "archived": {
            "type": "boolean"
          },
          "archivedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "translations": {
            "type": "object"
          }
        },
        "type": "object"
      },
      "StaffTicketTypeInput": {
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "price": {
            "properties": {
              "amount": {
                "description": "Cents",
                "type": "integer",
                "default": 0
              },
              "currency": {
                "description": "Default: the activity's currency",
                "type": "string",
                "nullable": true
              }
            },
            "type": "object"
          },
          "seatWeight": {
            "type": "integer",
            "default": 1,
            "enum": [
              0,
              1
            ]
          },
          "refundableOnLateCancel": {
            "type": "boolean",
            "default": false
          },
          "minPerBooking": {
            "type": "integer",
            "default": 0
          },
          "maxPerBooking": {
            "type": "integer",
            "nullable": true
          },
          "sortOrder": {
            "type": "integer",
            "nullable": true
          },
          "productCode": {
            "description": "Left out: unchanged",
            "type": "string",
            "nullable": true
          },
          "translations": {
            "description": "Left out: unchanged; {} clears them",
            "type": "object"
          }
        },
        "type": "object"
      },
      "StaffActivityHeadcount": {
        "description": "How an activity is booked: attendee mode, ticket types, extras and booking style (#786, #793, #794)",
        "properties": {
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "attendeeMode": {
            "type": "string",
            "enum": [
              "participants",
              "headcount"
            ]
          },
          "modeLocked": {
            "description": "The mode can't change while there are active bookings",
            "type": "boolean"
          },
          "activeBookings": {
            "type": "integer"
          },
          "headcount": {
            "$ref": "#/components/schemas/ActivityHeadcountSettings"
          },
          "ticketTypes": {
            "description": "For sale first by sort order, archived ones after",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffTicketType"
            }
          },
          "offeredExtraIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "productCode": {
            "type": "string",
            "nullable": true
          },
          "bookingStyle": {
            "type": "string",
            "enum": [
              "instant",
              "request",
              "slots"
            ]
          },
          "hire": {
            "$ref": "#/components/schemas/ActivityHireSettings"
          }
        },
        "type": "object"
      },
      "StaffErrorWithCode": {
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "description": "Stable error code",
            "type": "string"
          }
        },
        "type": "object"
      },
      "Address": {
        "description": "Physical address",
        "properties": {
          "street": {
            "type": "string",
            "example": "123 Main Street"
          },
          "city": {
            "type": "string",
            "example": "Amsterdam"
          },
          "postal_code": {
            "type": "string",
            "example": "1012 AB"
          },
          "country": {
            "type": "string",
            "example": "Netherlands"
          }
        },
        "type": "object"
      },
      "ApiKey": {
        "description": "An API key; the secret itself is only in the response that creates it.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "prefix": {
            "description": "The first characters of the secret, to recognise it",
            "type": "string",
            "example": "plek_live_3f9a1c"
          },
          "role": {
            "description": "The role whose permissions the key has (a temporary key: the role of the member who made it)",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastUsedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "revokedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "revoked": {
            "type": "boolean"
          },
          "temporary": {
            "description": "A one-hour test key of the developer portal",
            "type": "boolean"
          },
          "scope": {
            "description": "Temporary keys: read permissions only, or everything the member may do",
            "type": "string",
            "nullable": true,
            "enum": [
              "read",
              "write"
            ]
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "expired": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "SlotOffer": {
        "description": "How a service (an activity with bookingStyle \"slots\") is offered at one location: one offer per activity and location.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "locationId": {
            "type": "string",
            "format": "uuid"
          },
          "durationMinutes": {
            "description": "How long a moment lasts (5-1440)",
            "type": "integer"
          },
          "gridMinutes": {
            "description": "Moments start every N minutes from local midnight",
            "type": "integer"
          },
          "bufferBeforeMinutes": {
            "type": "integer"
          },
          "bufferAfterMinutes": {
            "type": "integer"
          },
          "leadTimeMinutes": {
            "description": "How long before it starts a moment can still be booked online",
            "type": "integer"
          },
          "horizonDays": {
            "description": "How far ahead moments can be booked online",
            "type": "integer"
          },
          "capacity": {
            "description": "Seats per moment",
            "type": "integer"
          },
          "price": {
            "description": "Its own price, {amount, currency}; null: the activity's default price",
            "type": "object",
            "nullable": true
          },
          "ticketPrices": {
            "description": "Ticket type id => {amount, currency}, for a headcount service",
            "type": "object"
          },
          "taxCategoryId": {
            "type": "string",
            "nullable": true
          },
          "resource": {
            "description": "What a moment takes: one staff member or one space of the pool",
            "type": "string",
            "enum": [
              "staff",
              "space"
            ]
          },
          "staffIds": {
            "description": "Narrows the activity's providers here; null: every provider",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "nullable": true
          },
          "spaceIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "pool": {
            "description": "Who or what a moment can take now, staff in provider order (\"anyone\" takes the first free one)",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "active": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "SlotOfferInput": {
        "description": "Creating needs activityId and locationId; on an update fields left out stay as they are.",
        "properties": {
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "locationId": {
            "type": "string",
            "format": "uuid"
          },
          "durationMinutes": {
            "type": "integer",
            "default": 60
          },
          "gridMinutes": {
            "type": "integer",
            "default": 30
          },
          "bufferBeforeMinutes": {
            "type": "integer",
            "default": 0
          },
          "bufferAfterMinutes": {
            "type": "integer",
            "default": 0
          },
          "leadTimeMinutes": {
            "type": "integer",
            "default": 0
          },
          "horizonDays": {
            "type": "integer",
            "default": 60
          },
          "capacity": {
            "type": "integer",
            "default": 1
          },
          "price": {
            "properties": {
              "amount": {
                "type": "integer"
              },
              "currency": {
                "type": "string"
              }
            },
            "type": "object",
            "nullable": true
          },
          "ticketPrices": {
            "type": "object",
            "nullable": true
          },
          "taxCategoryId": {
            "type": "string",
            "nullable": true
          },
          "resource": {
            "type": "string",
            "default": "staff",
            "enum": [
              "staff",
              "space"
            ]
          },
          "staffIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "nullable": true
          },
          "spaceIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "active": {
            "type": "boolean",
            "default": true
          }
        },
        "type": "object"
      },
      "StaffSlot": {
        "description": "A moment staff can book for a customer",
        "properties": {
          "start": {
            "description": "In the organisation's timezone with offset; send it back as `start` to book",
            "type": "string",
            "format": "date-time"
          },
          "end": {
            "type": "string",
            "format": "date-time"
          },
          "seatsLeft": {
            "type": "integer"
          },
          "resources": {
            "type": "array",
            "items": {
              "properties": {
                "id": {
                  "description": "Staff member or space",
                  "type": "string",
                  "format": "uuid"
                },
                "seatsLeft": {
                  "type": "integer"
                },
                "joinable": {
                  "description": "An existing booking at this moment has seats left",
                  "type": "boolean"
                }
              },
              "type": "object"
            }
          },
          "outsideSchedule": {
            "description": "Staff members free then but outside their schedule or opening hours: a warning, not a refusal",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "type": "object"
      },
      "AffectedAppointment": {
        "description": "A booked appointment still to come and whether its staff member still fits",
        "properties": {
          "sessionId": {
            "type": "string",
            "format": "uuid"
          },
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "activityName": {
            "type": "string"
          },
          "locationId": {
            "type": "string",
            "format": "uuid"
          },
          "locationName": {
            "type": "string"
          },
          "slotOfferId": {
            "type": "string",
            "format": "uuid"
          },
          "startTime": {
            "type": "string",
            "format": "date-time"
          },
          "endTime": {
            "type": "string",
            "format": "date-time"
          },
          "staffId": {
            "type": "string",
            "format": "uuid"
          },
          "staffName": {
            "type": "string"
          },
          "bookedCount": {
            "type": "integer"
          },
          "attendees": {
            "description": "Who it is booked for: participants, or a party's contact (#962)",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "not_member",
                "not_provider",
                "outside_schedule"
              ]
            }
          },
          "candidates": {
            "type": "array",
            "items": {
              "properties": {
                "userId": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "outsideSchedule": {
                  "type": "boolean"
                }
              },
              "type": "object"
            }
          }
        },
        "type": "object"
      },
      "CheckoutSlot": {
        "description": "A moment of a service booked at a free moment (#924), instead of sessionId/seriesId",
        "required": [
          "slotOfferId",
          "start"
        ],
        "properties": {
          "slotOfferId": {
            "type": "string",
            "format": "uuid"
          },
          "start": {
            "description": "Exactly as GET /activities/{id}/slots returns it",
            "type": "string",
            "format": "date-time"
          },
          "staffId": {
            "description": "Left out: the first free staff member in provider order",
            "type": "string",
            "format": "uuid",
            "nullable": true
          }
        },
        "type": "object"
      },
      "AppointmentMoveContext": {
        "description": "A booked appointment as staff move it (#933)",
        "properties": {
          "bookingId": {
            "type": "string",
            "format": "uuid"
          },
          "sessionId": {
            "type": "string",
            "format": "uuid"
          },
          "slotOfferId": {
            "type": "string",
            "format": "uuid"
          },
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "activityName": {
            "type": "string"
          },
          "locationId": {
            "type": "string",
            "format": "uuid"
          },
          "locationName": {
            "type": "string"
          },
          "timezone": {
            "type": "string"
          },
          "durationMinutes": {
            "type": "integer"
          },
          "startTime": {
            "type": "string",
            "format": "date-time"
          },
          "endTime": {
            "type": "string",
            "format": "date-time"
          },
          "resource": {
            "type": "string",
            "enum": [
              "staff",
              "space"
            ]
          },
          "staffId": {
            "description": "The staff member (or space) it is with",
            "type": "string",
            "format": "uuid"
          },
          "staffName": {
            "type": "string"
          },
          "canChangeStaff": {
            "description": "booking:write:all on a staff appointment",
            "type": "boolean"
          },
          "people": {
            "type": "array",
            "items": {
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "current": {
                  "type": "boolean"
                },
                "hidden": {
                  "description": "Keeps their name off the booking site",
                  "type": "boolean"
                }
              },
              "type": "object"
            }
          },
          "reference": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "seats": {
            "type": "integer"
          },
          "movable": {
            "description": "Active and still to come",
            "type": "boolean"
          },
          "customer": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "firstName": {
                "type": "string"
              }
            },
            "type": "object",
            "nullable": true
          },
          "channels": {
            "properties": {
              "email": {
                "type": "boolean"
              },
              "sms": {
                "description": "The organisation sends this by SMS and the customer opted in",
                "type": "boolean"
              },
              "push": {
                "description": "The customer turned on notifications on a device",
                "type": "boolean"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "AppointmentMoveSlots": {
        "properties": {
          "timezone": {
            "type": "string"
          },
          "staffId": {
            "type": "string",
            "format": "uuid"
          },
          "days": {
            "type": "array",
            "items": {
              "properties": {
                "date": {
                  "type": "string",
                  "format": "date"
                },
                "slots": {
                  "type": "array",
                  "items": {
                    "properties": {
                      "start": {
                        "description": "Send it back as `start`",
                        "type": "string",
                        "format": "date-time"
                      },
                      "end": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "outsideSchedule": {
                        "description": "Outside the availability or opening hours: allowed, with a warning",
                        "type": "boolean"
                      },
                      "current": {
                        "description": "Where the appointment is now",
                        "type": "boolean"
                      }
                    },
                    "type": "object"
                  }
                }
              },
              "type": "object"
            }
          }
        },
        "type": "object"
      },
      "AppointmentMoveRequest": {
        "required": [
          "start"
        ],
        "properties": {
          "start": {
            "description": "ISO 8601 with offset, as the moments list it",
            "type": "string",
            "format": "date-time"
          },
          "staffId": {
            "description": "Left out: the staff member it is with now",
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "notify": {
            "description": "Email (and SMS/push) the customer",
            "type": "boolean",
            "default": true
          },
          "note": {
            "description": "A personal note in the email",
            "type": "string",
            "nullable": true,
            "maxLength": 500
          }
        },
        "type": "object"
      },
      "AppointmentMovePreview": {
        "properties": {
          "customerName": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "properties": {
              "subject": {
                "type": "string"
              },
              "html": {
                "type": "string"
              },
              "text": {
                "type": "string"
              }
            },
            "type": "object",
            "nullable": true
          },
          "sms": {
            "type": "string",
            "nullable": true
          },
          "push": {
            "properties": {
              "title": {
                "type": "string"
              },
              "body": {
                "type": "string"
              },
              "devices": {
                "type": "integer"
              }
            },
            "type": "object",
            "nullable": true
          }
        },
        "type": "object"
      },
      "MovedAppointment": {
        "properties": {
          "bookingId": {
            "type": "string",
            "format": "uuid"
          },
          "sessionId": {
            "description": "The appointment's session now",
            "type": "string",
            "format": "uuid"
          },
          "previousSessionId": {
            "type": "string",
            "format": "uuid"
          },
          "startTime": {
            "type": "string",
            "format": "date-time"
          },
          "endTime": {
            "type": "string",
            "format": "date-time"
          },
          "date": {
            "description": "Local date",
            "type": "string",
            "format": "date"
          },
          "staffId": {
            "type": "string",
            "format": "uuid"
          },
          "staffName": {
            "type": "string"
          },
          "outsideSchedule": {
            "type": "boolean"
          },
          "notified": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "BookingParticipant": {
        "required": [
          "firstName",
          "lastName",
          "email"
        ],
        "properties": {
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "phone": {
            "type": "string",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffBookingAmount": {
        "description": "An amount in the smallest currency unit (cents) with its currency",
        "properties": {
          "amount": {
            "type": "integer",
            "example": 2500
          },
          "currency": {
            "type": "string",
            "example": "EUR"
          }
        },
        "type": "object"
      },
      "StaffBookingStringAmount": {
        "description": "An amount in cents as a numeric string, with its currency",
        "properties": {
          "amount": {
            "type": "string",
            "example": "2500"
          },
          "currency": {
            "type": "string",
            "example": "EUR"
          }
        },
        "type": "object"
      },
      "StaffBookingPartyLine": {
        "description": "One line of a party booked by headcount (#786): a ticket type or an extra (#793), as booked",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "ticket",
              "extra"
            ]
          },
          "ticketTypeId": {
            "description": "Tickets only",
            "type": "string",
            "format": "uuid"
          },
          "extraId": {
            "description": "Extras only",
            "type": "string",
            "format": "uuid"
          },
          "unit": {
            "description": "Extras only",
            "type": "string",
            "enum": [
              "per_person",
              "per_piece",
              "flat"
            ]
          },
          "name": {
            "type": "string"
          },
          "quantity": {
            "type": "integer"
          },
          "unitPrice": {
            "$ref": "#/components/schemas/StaffBookingAmount"
          },
          "seatWeight": {
            "description": "Seats one of these takes (0 or 1)",
            "type": "integer"
          },
          "refundable": {
            "description": "Refunded on a late cancellation",
            "type": "boolean"
          },
          "productCode": {
            "type": "string",
            "nullable": true
          },
          "total": {
            "$ref": "#/components/schemas/StaffBookingAmount"
          }
        },
        "type": "object"
      },
      "StaffBookingPartyContact": {
        "description": "The contact typed for a party",
        "properties": {
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "phone": {
            "type": "string",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffBookingTicketInput": {
        "required": [
          "ticketTypeId",
          "quantity"
        ],
        "properties": {
          "ticketTypeId": {
            "type": "string",
            "format": "uuid"
          },
          "quantity": {
            "type": "integer",
            "minimum": 0
          }
        },
        "type": "object"
      },
      "StaffBookingExtraInput": {
        "required": [
          "extraId",
          "quantity"
        ],
        "properties": {
          "extraId": {
            "type": "string",
            "format": "uuid"
          },
          "quantity": {
            "type": "integer",
            "minimum": 0
          }
        },
        "type": "object"
      },
      "StaffBooking": {
        "description": "A booking of one session or a whole series, for one participant or (participantId null) a party booked by headcount",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "participantId": {
            "description": "Null for a party",
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "partySize": {
            "description": "People in a party (every ticket); null for a participant booking",
            "type": "integer",
            "nullable": true
          },
          "seats": {
            "description": "Seats taken: 1 for a participant, the seat weight of the tickets for a party",
            "type": "integer"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffBookingPartyLine"
            },
            "nullable": true
          },
          "customerId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "partyContact": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffBookingPartyContact"
              }
            ],
            "nullable": true
          },
          "organisationId": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "enum": [
              "session",
              "series"
            ]
          },
          "seriesId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sessionId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "bookedPrice": {
            "$ref": "#/components/schemas/StaffBookingStringAmount"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "confirmed",
              "cancelled",
              "expired"
            ]
          },
          "bookedAt": {
            "type": "string",
            "format": "date-time"
          },
          "confirmedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "cancelledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "cancellationReason": {
            "type": "string",
            "nullable": true
          },
          "expiresAt": {
            "description": "Until when a pending booking holds its seat",
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "rescheduledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "isTrial": {
            "type": "boolean"
          },
          "trialBookingId": {
            "description": "The trial this series booking was converted from",
            "type": "string",
            "format": "uuid",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffBookingBalance": {
        "description": "What is still to be paid (#397, #723)",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "not_required",
              "paid",
              "partially_paid",
              "unpaid"
            ]
          },
          "due": {
            "$ref": "#/components/schemas/StaffBookingAmount"
          },
          "paid": {
            "$ref": "#/components/schemas/StaffBookingAmount"
          },
          "outstanding": {
            "$ref": "#/components/schemas/StaffBookingAmount"
          },
          "overdue": {
            "description": "Paid later and past its deadline",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "StaffBookingListItem": {
        "description": "A booking row of the staff list. Amounts and payments are null without payment:read.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "reference": {
            "description": "The short reference customers see, e.g. TT-4F7K2M",
            "type": "string",
            "nullable": true
          },
          "participantId": {
            "description": "Null for a party; participantName is then its contact",
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "customerId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "partySize": {
            "type": "integer",
            "nullable": true
          },
          "seats": {
            "type": "integer"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffBookingPartyLine"
            },
            "nullable": true
          },
          "partyContact": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffBookingPartyContact"
              }
            ],
            "nullable": true
          },
          "organisationId": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "enum": [
              "session",
              "series"
            ]
          },
          "seriesId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sessionId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "bookedPrice": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffBookingStringAmount"
              }
            ],
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "confirmed",
              "cancelled",
              "expired"
            ]
          },
          "bookedAt": {
            "type": "string",
            "format": "date-time"
          },
          "confirmedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "cancelledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "participantName": {
            "type": "string",
            "nullable": true
          },
          "customerName": {
            "type": "string",
            "nullable": true
          },
          "customerEmail": {
            "type": "string",
            "nullable": true
          },
          "customerEmailStatus": {
            "type": "string",
            "nullable": true,
            "enum": [
              "verified",
              "unverified",
              "bounced",
              "complained"
            ]
          },
          "sessionStartTime": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "appointment": {
            "description": "A session booked at a free moment (#924)",
            "type": "boolean"
          },
          "entitlement": {
            "description": "The pass, subscription, perk or make-up credit that paid for it (#374, #935); givenBack once the use was given back",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "sourceType": {
                "type": "string",
                "enum": [
                  "absence",
                  "subscription",
                  "pass",
                  "series_perk",
                  "manual"
                ]
              },
              "givenBack": {
                "type": "boolean"
              }
            },
            "type": "object",
            "nullable": true
          },
          "seriesName": {
            "type": "string",
            "nullable": true
          },
          "activityName": {
            "type": "string",
            "nullable": true
          },
          "paymentStatus": {
            "type": "string",
            "nullable": true
          },
          "balance": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffBookingBalance"
              }
            ],
            "nullable": true,
            "description": "Confirmed bookings only (state, outstanding, overdue); null without payment:read"
          },
          "offlinePayment": {
            "description": "Paid later (on site / bank transfer); null without payment:read",
            "properties": {
              "method": {
                "type": "string",
                "enum": [
                  "on_site",
                  "bank_transfer"
                ]
              },
              "dueAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "reminderSentAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "isTrial": {
            "type": "boolean"
          },
          "trialConverted": {
            "type": "boolean"
          },
          "convertedFromTrial": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "StaffBookingDetail": {
        "description": "One booking as the staff booking page shows it",
        "allOf": [
          {
            "$ref": "#/components/schemas/StaffBooking"
          },
          {
            "properties": {
              "reference": {
                "type": "string",
                "nullable": true
              },
              "participantName": {
                "type": "string",
                "nullable": true
              },
              "customerName": {
                "type": "string",
                "nullable": true
              },
              "customerEmail": {
                "type": "string",
                "nullable": true
              },
              "customerEmailStatus": {
                "type": "string",
                "nullable": true
              },
              "sessionStartTime": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "appointment": {
                "type": "boolean"
              },
              "seriesName": {
                "type": "string",
                "nullable": true
              },
              "activityName": {
                "type": "string",
                "nullable": true
              },
              "paymentStatus": {
                "type": "string",
                "nullable": true
              },
              "trialConverted": {
                "type": "boolean"
              },
              "convertedFromTrial": {
                "type": "boolean"
              },
              "originalPrice": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/StaffBookingStringAmount"
                  }
                ],
                "nullable": true
              },
              "discount": {
                "properties": {
                  "code": {
                    "type": "string",
                    "nullable": true
                  },
                  "amount": {
                    "$ref": "#/components/schemas/StaffBookingStringAmount"
                  }
                },
                "type": "object",
                "nullable": true
              },
              "coversFrom": {
                "description": "A late sign-up covers the series' sessions from then (#340)",
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "siblingDiscount": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/StaffBookingStringAmount"
                  }
                ],
                "nullable": true
              },
              "giftCardTotal": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/StaffBookingStringAmount"
                  }
                ],
                "nullable": true
              },
              "giftCards": {
                "type": "array",
                "items": {
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "amount": {
                      "$ref": "#/components/schemas/StaffBookingStringAmount"
                    },
                    "redeemedAt": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    },
                    "refundedAt": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              },
              "receiptAvailable": {
                "description": "GET /bookings/{id}/receipt.pdf works",
                "type": "boolean"
              },
              "cancellation": {
                "description": "What the customer would get under the cancellation policy, as a hint for staff",
                "properties": {
                  "allowed": {
                    "type": "boolean"
                  },
                  "reason": {
                    "type": "string",
                    "nullable": true
                  },
                  "refundPercentage": {
                    "type": "integer"
                  },
                  "freeUntil": {
                    "type": "string",
                    "format": "date-time"
                  }
                },
                "type": "object",
                "nullable": true
              },
              "balance": {
                "$ref": "#/components/schemas/StaffBookingBalance"
              },
              "offlinePayment": {
                "properties": {
                  "method": {
                    "type": "string",
                    "enum": [
                      "on_site",
                      "bank_transfer"
                    ]
                  },
                  "reference": {
                    "description": "Structured communication for a bank transfer",
                    "type": "string",
                    "nullable": true
                  },
                  "dueAt": {
                    "type": "string",
                    "format": "date-time",
                    "nullable": true
                  },
                  "reminderSentAt": {
                    "type": "string",
                    "format": "date-time",
                    "nullable": true
                  }
                },
                "type": "object",
                "nullable": true
              },
              "answers": {
                "description": "Booking questions and consents as answered (#395)",
                "type": "array",
                "items": {
                  "type": "object"
                }
              }
            },
            "type": "object"
          }
        ]
      },
      "StaffBookingPagination": {
        "properties": {
          "total": {
            "type": "integer"
          },
          "per_page": {
            "type": "integer"
          },
          "current_page": {
            "type": "integer"
          },
          "total_pages": {
            "type": "integer"
          },
          "has_more": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "StaffBookingReserveInput": {
        "description": "Staff book a participant: sessionId for a session (session/reserve) or seriesId for a series (series/reserve)",
        "required": [
          "participantId"
        ],
        "properties": {
          "participantId": {
            "type": "string",
            "format": "uuid"
          },
          "sessionId": {
            "description": "session/reserve only (required there)",
            "type": "string",
            "format": "uuid"
          },
          "seriesId": {
            "description": "series/reserve only (required there)",
            "type": "string",
            "format": "uuid"
          },
          "reservationMinutes": {
            "description": "How long the pending booking holds its seat",
            "type": "integer",
            "default": 15
          },
          "trial": {
            "description": "session/reserve only: a trial class (#402)",
            "type": "boolean",
            "default": false
          },
          "ignoreAgeRules": {
            "description": "Staff confirmed booking someone outside the age requirement (#352)",
            "type": "boolean",
            "default": false
          },
          "spotId": {
            "description": "A spot picked by staff (#496), else the first free one",
            "type": "string",
            "format": "uuid",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffBookingPayment": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "bookingId": {
            "type": "string",
            "format": "uuid"
          },
          "organisationId": {
            "type": "string",
            "format": "uuid"
          },
          "amount": {
            "$ref": "#/components/schemas/StaffBookingStringAmount"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "paid",
              "failed",
              "refunded",
              "partially_refunded",
              "expired",
              "cancelled"
            ]
          },
          "method": {
            "description": "e.g. creditcard, bancontact, banktransfer, cash, cardterminal, other, unknown",
            "type": "string"
          },
          "gatewayName": {
            "type": "string",
            "nullable": true
          },
          "gatewayTransactionId": {
            "type": "string",
            "nullable": true
          },
          "checkoutUrl": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "paidAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "failedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "refundedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "refundedAmount": {
            "$ref": "#/components/schemas/StaffBookingStringAmount"
          },
          "refundableAmount": {
            "$ref": "#/components/schemas/StaffBookingStringAmount"
          },
          "canBeRefunded": {
            "type": "boolean"
          },
          "manual": {
            "description": "Paid outside the gateway: a refund is paid back by hand and only recorded (#784)",
            "type": "boolean"
          },
          "refunds": {
            "type": "array",
            "items": {
              "properties": {
                "id": {
                  "type": "string"
                },
                "amount": {
                  "$ref": "#/components/schemas/StaffBookingStringAmount"
                },
                "description": {
                  "type": "string",
                  "nullable": true
                },
                "refundedAt": {
                  "type": "string",
                  "format": "date-time"
                }
              },
              "type": "object"
            }
          }
        },
        "type": "object"
      },
      "StaffBookingMoved": {
        "description": "A booking after a reschedule or transfer",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "enum": [
              "session",
              "series"
            ]
          },
          "status": {
            "type": "string"
          },
          "participantId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sessionId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "seriesId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "rescheduledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffBookingRescheduleOptions": {
        "properties": {
          "sessions": {
            "description": "Sessions of the same activity to come",
            "type": "array",
            "items": {
              "properties": {
                "sessionId": {
                  "type": "string",
                  "format": "uuid"
                },
                "startTime": {
                  "type": "string",
                  "format": "date-time"
                },
                "endTime": {
                  "type": "string",
                  "format": "date-time"
                },
                "activity": {
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string",
                      "nullable": true
                    }
                  },
                  "type": "object"
                },
                "location": {
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string",
                      "nullable": true
                    }
                  },
                  "type": "object"
                },
                "seriesName": {
                  "type": "string",
                  "nullable": true
                },
                "coachNames": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "spaces": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "capacity": {
                  "type": "integer"
                },
                "availableSpots": {
                  "type": "integer"
                },
                "price": {
                  "$ref": "#/components/schemas/StaffBookingAmount"
                },
                "samePrice": {
                  "type": "boolean"
                },
                "alreadyBooked": {
                  "description": "The participant already holds it",
                  "type": "boolean"
                },
                "bookableOnItsOwn": {
                  "type": "boolean"
                }
              },
              "type": "object"
            }
          },
          "series": {
            "description": "Other series of the same activity (series bookings)",
            "type": "array",
            "items": {
              "properties": {
                "seriesId": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "locationId": {
                  "type": "string",
                  "format": "uuid"
                },
                "startDate": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                },
                "endDate": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                },
                "availableSpots": {
                  "type": "integer"
                },
                "bookable": {
                  "type": "boolean"
                },
                "price": {
                  "$ref": "#/components/schemas/StaffBookingAmount"
                },
                "samePrice": {
                  "type": "boolean"
                },
                "alreadyBooked": {
                  "type": "boolean"
                }
              },
              "type": "object"
            }
          },
          "siblings": {
            "description": "Transfer suggestions: the other participants of the same customers",
            "type": "array",
            "items": {
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                }
              },
              "type": "object"
            }
          }
        },
        "type": "object"
      },
      "StaffPartyBooking": {
        "description": "A party booked by headcount (#786)",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "participantId": {
            "description": "Always null",
            "type": "string",
            "nullable": true
          },
          "customerId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "organisationId": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "enum": [
              "session"
            ]
          },
          "sessionId": {
            "type": "string",
            "format": "uuid"
          },
          "seriesId": {
            "description": "Always null",
            "type": "string",
            "nullable": true
          },
          "partySize": {
            "type": "integer"
          },
          "seats": {
            "type": "integer"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffBookingPartyLine"
            }
          },
          "partyContact": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffBookingPartyContact"
              }
            ],
            "nullable": true
          },
          "bookedPrice": {
            "$ref": "#/components/schemas/StaffBookingAmount"
          },
          "originalPrice": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffBookingAmount"
              }
            ],
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "confirmed",
              "cancelled",
              "expired"
            ]
          },
          "bookedAt": {
            "type": "string",
            "format": "date-time"
          },
          "confirmedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "offlinePaymentMethod": {
            "type": "string",
            "nullable": true,
            "enum": [
              "on_site",
              "bank_transfer"
            ]
          }
        },
        "type": "object"
      },
      "StaffPartyBookingInput": {
        "description": "customerId, or customer to find or create the account by email",
        "required": [
          "sessionId",
          "tickets"
        ],
        "properties": {
          "sessionId": {
            "type": "string",
            "format": "uuid"
          },
          "tickets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffBookingTicketInput"
            }
          },
          "extras": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffBookingExtraInput"
            }
          },
          "customerId": {
            "type": "string",
            "format": "uuid"
          },
          "customer": {
            "properties": {
              "email": {
                "type": "string",
                "format": "email"
              },
              "firstName": {
                "type": "string"
              },
              "lastName": {
                "type": "string"
              },
              "phone": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object"
          },
          "contact": {
            "description": "Defaults to the customer's name and phone",
            "properties": {
              "firstName": {
                "type": "string"
              },
              "lastName": {
                "type": "string"
              },
              "phone": {
                "type": "string"
              }
            },
            "type": "object"
          },
          "discountCode": {
            "type": "string",
            "nullable": true
          },
          "waivePrice": {
            "type": "boolean",
            "default": false
          },
          "allowOverbooking": {
            "type": "boolean",
            "default": false
          },
          "reservationMinutes": {
            "type": "integer",
            "default": 15,
            "maximum": 1440,
            "minimum": 1
          },
          "offlinePayment": {
            "description": "Confirms the party, paid later",
            "type": "string",
            "nullable": true,
            "enum": [
              "on_site",
              "bank_transfer"
            ]
          }
        },
        "type": "object"
      },
      "StaffPartyChangeInput": {
        "description": "The whole new party; sessionId moves it to another session of the same activity",
        "required": [
          "tickets"
        ],
        "properties": {
          "tickets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffBookingTicketInput"
            }
          },
          "extras": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffBookingExtraInput"
            }
          },
          "sessionId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "allowOverbooking": {
            "type": "boolean",
            "default": false
          },
          "notify": {
            "description": "Email the customer when the party moves",
            "type": "boolean",
            "default": true
          },
          "locale": {
            "description": "Email language; default from Accept-Language",
            "type": "string",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffPartyChange": {
        "description": "What a party change does; the difference is charged or refunded by hand",
        "properties": {
          "sessionId": {
            "type": "string",
            "format": "uuid"
          },
          "moved": {
            "type": "boolean"
          },
          "before": {
            "properties": {
              "lines": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/StaffBookingPartyLine"
                }
              },
              "seats": {
                "type": "integer"
              },
              "partySize": {
                "type": "integer"
              },
              "price": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/StaffBookingAmount"
                  }
                ],
                "nullable": true
              }
            },
            "type": "object"
          },
          "after": {
            "properties": {
              "lines": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/StaffBookingPartyLine"
                }
              },
              "seats": {
                "type": "integer"
              },
              "partySize": {
                "type": "integer"
              },
              "price": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/StaffBookingAmount"
                  }
                ],
                "nullable": true
              },
              "discount": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/StaffBookingAmount"
                  }
                ],
                "nullable": true
              }
            },
            "type": "object"
          },
          "difference": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffBookingAmount"
              }
            ],
            "nullable": true,
            "description": "Positive: to charge, negative: to refund"
          },
          "late": {
            "description": "After the free-cancellation cut-off",
            "type": "boolean"
          },
          "lateNonRefundable": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffBookingAmount"
              }
            ],
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffErrorWithType": {
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "type": {
            "description": "Stable error code",
            "type": "string"
          }
        },
        "type": "object"
      },
      "BookkeepingProvider": {
        "description": "Software that can be connected and how",
        "properties": {
          "id": {
            "type": "string",
            "enum": [
              "billit",
              "yuki"
            ]
          },
          "label": {
            "type": "string",
            "example": "Billit"
          },
          "connect": {
            "description": "oauth: through the software's login (POST /bookkeeping/oauth/start); credentials: a key typed in (POST /bookkeeping)",
            "type": "string",
            "enum": [
              "oauth",
              "credentials"
            ]
          },
          "oauthConfigured": {
            "description": "False when the platform has no OAuth client for it here",
            "type": "boolean"
          },
          "oauthSimulated": {
            "description": "Outside production the login is simulated",
            "type": "boolean"
          },
          "credentialsAllowed": {
            "description": "Whether a key may be typed in instead of the login",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "BookkeepingConnection": {
        "description": "The connection; credentials are never returned",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "provider": {
            "type": "string",
            "enum": [
              "billit",
              "yuki"
            ]
          },
          "providerLabel": {
            "type": "string"
          },
          "status": {
            "description": "failing: the software refused the credentials; pushing pauses until a connection test works",
            "type": "string",
            "enum": [
              "active",
              "failing"
            ]
          },
          "settings": {
            "description": "Billit: {partyId, sandbox}; Yuki: {administrationId, administrationName, glAccountCode, glAccounts, vatTypes}",
            "type": "object"
          },
          "syncFrom": {
            "description": "Sales paid from this moment are pushed",
            "type": "string",
            "format": "date-time"
          },
          "syncedUntil": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "lastSyncAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "lastError": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "counts": {
            "$ref": "#/components/schemas/BookkeepingCounts"
          },
          "maxAttempts": {
            "description": "Attempts before a document stays failed until retried",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "BookkeepingCounts": {
        "description": "Documents per sync status",
        "type": "object",
        "example": {
          "pending": 2,
          "sent": 120,
          "failed": 0,
          "skipped": 4
        },
        "additionalProperties": {
          "type": "integer"
        }
      },
      "BookkeepingConnectInput": {
        "required": [
          "provider",
          "credentials"
        ],
        "properties": {
          "provider": {
            "type": "string",
            "enum": [
              "billit",
              "yuki"
            ]
          },
          "credentials": {
            "description": "Billit: {apiKey} (only where credentialsAllowed); Yuki: {accessKey}",
            "type": "object"
          },
          "settings": {
            "description": "Billit: {partyId, sandbox}; Yuki: {administrationId, administrationName?, glAccountCode?, glAccounts? {productCode: glAccount}, vatTypes? {\"21\": 1}}",
            "type": "object"
          },
          "syncFrom": {
            "description": "Push sales paid from this date (default: now)",
            "type": "string",
            "format": "date",
            "nullable": true
          }
        },
        "type": "object"
      },
      "BookkeepingAccount": {
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "BookkeepingLogDocument": {
        "description": "One document (sales invoice or credit note) pushed or to push",
        "properties": {
          "id": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "invoice",
              "credit_note"
            ]
          },
          "source": {
            "description": "Source of the sale (booking, plan_purchase, gift_card_order, fee, hire)",
            "type": "string"
          },
          "references": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "date": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "sent",
              "failed",
              "skipped"
            ]
          },
          "attempts": {
            "type": "integer"
          },
          "externalId": {
            "description": "The document's id in the software",
            "type": "string",
            "nullable": true
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "nextAttemptAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "sentAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "customer": {
            "description": "Only with customer:read",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              },
              "name": {
                "type": "string",
                "nullable": true
              },
              "email": {
                "type": "string",
                "nullable": true
              },
              "company": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "total": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffAmount"
              }
            ],
            "nullable": true,
            "description": "Only with payment:read"
          },
          "lines": {
            "type": "array",
            "items": {
              "properties": {
                "description": {
                  "type": "string"
                },
                "productCode": {
                  "type": "string",
                  "nullable": true
                },
                "quantity": {
                  "type": "integer"
                },
                "taxRate": {
                  "description": "Basis points (2100 = 21%)",
                  "type": "integer",
                  "nullable": true
                },
                "gross": {
                  "description": "Cents, only with payment:read",
                  "type": "integer",
                  "nullable": true
                }
              },
              "type": "object"
            }
          }
        },
        "type": "object"
      },
      "StaffClosurePeriod": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "example": "Autumn half term"
          },
          "startDate": {
            "type": "string",
            "format": "date"
          },
          "endDate": {
            "description": "Inclusive",
            "type": "string",
            "format": "date"
          },
          "locationId": {
            "description": "Null: the whole organisation",
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffClosurePeriodInput": {
        "required": [
          "name",
          "startDate",
          "endDate"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "startDate": {
            "type": "string",
            "format": "date"
          },
          "endDate": {
            "type": "string",
            "format": "date"
          },
          "locationId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "timezone": {
            "description": "For the sessions inside it in the answer; default UTC",
            "type": "string",
            "example": "Europe/Brussels"
          }
        },
        "type": "object"
      },
      "StaffClosureAffectedSession": {
        "description": "A scheduled session inside a closure (not cancelled by it)",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "seriesId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "activityName": {
            "type": "string",
            "nullable": true
          },
          "locationId": {
            "type": "string",
            "format": "uuid"
          },
          "startTime": {
            "type": "string",
            "format": "date-time"
          },
          "endTime": {
            "type": "string",
            "format": "date-time"
          },
          "bookedCount": {
            "type": "integer"
          }
        },
        "type": "object"
      },
      "CoachPerson": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "fullName": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "CoachWeeklyAvailability": {
        "description": "ISO weekday (\"1\" = Monday … \"7\") => time slots in the organisation's timezone; \"24:00\" ends the day. Weekdays left out: not available.",
        "type": "object",
        "example": {
          "1": [
            {
              "start": "09:00",
              "end": "12:30"
            }
          ],
          "3": [
            {
              "start": "17:00",
              "end": "21:00"
            }
          ]
        },
        "additionalProperties": {
          "type": "array",
          "items": {
            "properties": {
              "start": {
                "type": "string",
                "example": "09:00"
              },
              "end": {
                "type": "string",
                "example": "12:30"
              }
            },
            "type": "object"
          }
        }
      },
      "CoachAvailabilityException": {
        "description": "Time off (unavailable) or extra availability (available): whole days, or the same times on every day of the range",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "unavailable",
              "available"
            ]
          },
          "startDate": {
            "type": "string",
            "format": "date"
          },
          "endDate": {
            "type": "string",
            "format": "date"
          },
          "startTime": {
            "type": "string",
            "example": "13:00",
            "nullable": true
          },
          "endTime": {
            "type": "string",
            "example": "17:00",
            "nullable": true
          },
          "note": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "clashes": {
            "description": "Time off only: upcoming sessions the coach is down for then",
            "type": "array",
            "items": {
              "properties": {
                "sessionId": {
                  "type": "string",
                  "format": "uuid"
                },
                "activityName": {
                  "type": "string"
                },
                "startTime": {
                  "type": "string",
                  "format": "date-time"
                },
                "endTime": {
                  "type": "string",
                  "format": "date-time"
                },
                "substitutionRequested": {
                  "description": "An open substitute request exists for this coach",
                  "type": "boolean"
                }
              },
              "type": "object"
            }
          }
        },
        "type": "object"
      },
      "CoachAvailabilityVerdict": {
        "properties": {
          "status": {
            "description": "unknown: no weekly pattern",
            "type": "string",
            "enum": [
              "available",
              "outside_pattern",
              "unavailable",
              "unknown"
            ]
          },
          "note": {
            "description": "The time off's note",
            "type": "string",
            "nullable": true
          }
        },
        "type": "object"
      },
      "CoachPayRate": {
        "description": "A coach's pay from a local date on, in minor units (#741)",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "effectiveFrom": {
            "type": "string",
            "format": "date"
          },
          "currency": {
            "type": "string",
            "example": "EUR"
          },
          "perHour": {
            "type": "integer"
          },
          "perSession": {
            "type": "integer"
          },
          "perAttendee": {
            "description": "Per attendee checked in",
            "type": "integer"
          },
          "note": {
            "type": "string",
            "nullable": true
          }
        },
        "type": "object"
      },
      "CoachSubstituteCandidate": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "fullName": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "nullable": true
          },
          "conflicts": {
            "description": "Their own sessions overlapping this one",
            "type": "array",
            "items": {
              "properties": {
                "sessionId": {
                  "type": "string",
                  "format": "uuid"
                },
                "activityName": {
                  "type": "string"
                },
                "startTime": {
                  "type": "string",
                  "format": "date-time"
                },
                "endTime": {
                  "type": "string",
                  "format": "date-time"
                }
              },
              "type": "object"
            }
          },
          "availability": {
            "$ref": "#/components/schemas/CoachAvailabilityVerdict"
          }
        },
        "type": "object"
      },
      "CustomerAccountCustomer": {
        "description": "The signed-in customer",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "phone": {
            "description": "E.164 when it is a valid number",
            "type": "string",
            "nullable": true
          },
          "language": {
            "description": "The language emails are sent in (en, nl, fr, de)",
            "type": "string",
            "nullable": true
          },
          "emailVerified": {
            "description": "False for an account made by the booking form until the customer signs in with an emailed code or link",
            "type": "boolean"
          },
          "hasPassword": {
            "type": "boolean"
          },
          "marketingOptIn": {
            "description": "News and offers from the organisation",
            "type": "boolean"
          },
          "smsOptIn": {
            "description": "Text messages (off unless opted in)",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "CustomerAccountCustomerResponse": {
        "properties": {
          "customer": {
            "$ref": "#/components/schemas/CustomerAccountCustomer"
          }
        },
        "type": "object"
      },
      "CustomerAccountBillingDetailsResponse": {
        "properties": {
          "billingDetails": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffBillingDetails"
              }
            ],
            "nullable": true,
            "description": "Null when none are stored"
          }
        },
        "type": "object"
      },
      "CustomerParticipant": {
        "description": "A person the customer books for. Staff-only data (internal notes, identifiers) is never included.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "dateOfBirth": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "remarks": {
            "type": "string",
            "nullable": true
          },
          "customFields": {
            "description": "The organisation's participant custom fields customers may see, by field name",
            "type": "object",
            "additionalProperties": true
          },
          "alreadyBooked": {
            "description": "Only when listed with sessionId or seriesId: the participant already holds a seat there (#685)",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "CustomerParticipantInput": {
        "description": "On an update, fields left out keep their value; custom fields left out are kept.",
        "required": [
          "firstName",
          "lastName",
          "dateOfBirth"
        ],
        "properties": {
          "firstName": {
            "type": "string",
            "maxLength": 100
          },
          "lastName": {
            "type": "string",
            "maxLength": 100
          },
          "dateOfBirth": {
            "description": "YYYY-MM-DD, not in the future",
            "type": "string",
            "format": "date"
          },
          "remarks": {
            "type": "string",
            "nullable": true,
            "maxLength": 1000
          },
          "customFields": {
            "description": "Participant custom field values by field name",
            "type": "object",
            "additionalProperties": true
          }
        },
        "type": "object"
      },
      "CustomerParticipantValidationError": {
        "properties": {
          "error": {
            "type": "string"
          },
          "errors": {
            "description": "Message per field; custom fields as `customFields.{name}`",
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        },
        "type": "object"
      },
      "CustomerCalendarFeedLinks": {
        "properties": {
          "url": {
            "description": "The feed (.ics); secret: whoever has it sees the bookings",
            "type": "string",
            "format": "uri"
          },
          "webcalUrl": {
            "description": "The same as webcal://, opens the calendar app",
            "type": "string"
          },
          "googleUrl": {
            "description": "Subscribe in Google Calendar",
            "type": "string",
            "format": "uri"
          },
          "outlookUrl": {
            "description": "Subscribe in Outlook",
            "type": "string",
            "format": "uri"
          }
        },
        "type": "object"
      },
      "CustomerCalendarFeedResponse": {
        "properties": {
          "feed": {
            "description": "Null when the customer has no feed",
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/CustomerCalendarFeedLinks"
              },
              {
                "properties": {
                  "createdAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "wholeSchedule": {
                    "description": "Always null for customers",
                    "type": "object",
                    "nullable": true
                  }
                },
                "type": "object"
              }
            ]
          }
        },
        "type": "object"
      },
      "CustomerPushDevice": {
        "properties": {
          "id": {
            "type": "string"
          },
          "label": {
            "description": "The device, from its user agent",
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastUsedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "CustomerErrorWithCode": {
        "properties": {
          "error": {
            "description": "Message for people",
            "type": "string"
          },
          "code": {
            "description": "Stable code for clients",
            "type": "string"
          }
        },
        "type": "object"
      },
      "CustomerBookingAmount": {
        "properties": {
          "amount": {
            "description": "Minor units (cents)",
            "type": "integer"
          },
          "currency": {
            "type": "string",
            "example": "EUR"
          }
        },
        "type": "object"
      },
      "CustomerBookingPartyLine": {
        "description": "A ticket or extra of a party booking (#786, #793), as booked",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "ticket",
              "extra"
            ]
          },
          "ticketTypeId": {
            "description": "Tickets only",
            "type": "string",
            "format": "uuid"
          },
          "extraId": {
            "description": "Extras only",
            "type": "string",
            "format": "uuid"
          },
          "unit": {
            "description": "Extras only",
            "type": "string",
            "enum": [
              "per_person",
              "per_piece",
              "flat"
            ]
          },
          "name": {
            "type": "string"
          },
          "quantity": {
            "type": "integer"
          },
          "unitPrice": {
            "$ref": "#/components/schemas/CustomerBookingAmount"
          },
          "seatWeight": {
            "type": "integer"
          },
          "refundable": {
            "description": "Refunded on a late cancellation",
            "type": "boolean"
          },
          "productCode": {
            "type": "string",
            "nullable": true
          },
          "total": {
            "$ref": "#/components/schemas/CustomerBookingAmount"
          }
        },
        "type": "object"
      },
      "CustomerBookingCancellation": {
        "description": "What cancelling now would mean under the cancellation policy (#391)",
        "properties": {
          "allowed": {
            "type": "boolean"
          },
          "reason": {
            "type": "string"
          },
          "refundPercentage": {
            "type": "integer"
          },
          "freeUntil": {
            "description": "Free cancellation until then",
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "CustomerBookingRescheduleDecision": {
        "description": "Whether the customer may move this booking to another session themselves (#392)",
        "properties": {
          "allowed": {
            "type": "boolean"
          },
          "reason": {
            "type": "string",
            "enum": [
              "allowed",
              "not_a_session",
              "not_confirmed",
              "appointment",
              "window_passed",
              "limit_reached"
            ]
          },
          "until": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "CustomerBooking": {
        "description": "One of the customer's bookings: a session, a series or a party (headcount booking, participant null)",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "reference": {
            "description": "Short reference for people",
            "type": "string",
            "example": "TT-4F7K2M"
          },
          "type": {
            "type": "string",
            "enum": [
              "session",
              "series"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "confirmed",
              "cancelled",
              "expired"
            ]
          },
          "bookedPrice": {
            "$ref": "#/components/schemas/CustomerBookingAmount"
          },
          "participant": {
            "description": "Null for a party",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "firstName": {
                "type": "string"
              },
              "lastName": {
                "type": "string"
              }
            },
            "type": "object",
            "nullable": true
          },
          "partySize": {
            "description": "A party: the number of people",
            "type": "integer",
            "nullable": true
          },
          "seats": {
            "description": "Seats the booking takes",
            "type": "integer"
          },
          "lines": {
            "description": "A party: its tickets and extras",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CustomerBookingPartyLine"
            },
            "nullable": true
          },
          "session": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "startTime": {
                "type": "string",
                "format": "date-time"
              },
              "endTime": {
                "type": "string",
                "format": "date-time"
              }
            },
            "type": "object",
            "nullable": true
          },
          "series": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "startDate": {
                "type": "string",
                "format": "date",
                "nullable": true
              },
              "endDate": {
                "type": "string",
                "format": "date",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "location": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "address": {
                "description": "One line",
                "type": "string",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "startsAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "endsAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "receiptAvailable": {
            "description": "A receipt can be downloaded at /bookings/{id}/receipt.pdf",
            "type": "boolean"
          },
          "activity": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              }
            },
            "type": "object",
            "nullable": true
          },
          "bookedAt": {
            "type": "string",
            "format": "date-time"
          },
          "confirmedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "cancelledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "cancellation": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CustomerBookingCancellation"
              }
            ],
            "nullable": true,
            "description": "Null when the booking is no longer active"
          },
          "cancellationPolicy": {
            "description": "The policy that applies (freeCancellationHours, lateCancellation, lateRefundPercentage, lateCancellationFee, noShowFee, entitlementBookings)",
            "type": "object",
            "nullable": true
          },
          "offlinePayment": {
            "description": "Paid later on site or by bank transfer (#397): how to pay",
            "type": "object",
            "nullable": true
          },
          "balance": {
            "description": "Paid later: what is due, paid and still outstanding",
            "properties": {
              "state": {
                "type": "string"
              },
              "due": {
                "$ref": "#/components/schemas/CustomerBookingAmount"
              },
              "paid": {
                "$ref": "#/components/schemas/CustomerBookingAmount"
              },
              "outstanding": {
                "$ref": "#/components/schemas/CustomerBookingAmount"
              },
              "overdue": {
                "type": "boolean"
              }
            },
            "type": "object",
            "nullable": true
          },
          "canPayOnline": {
            "description": "Confirmed, paid later and still owing, and a gateway takes the currency: POST /bookings/{id}/pay",
            "type": "boolean"
          },
          "reschedule": {
            "$ref": "#/components/schemas/CustomerBookingRescheduleDecision"
          },
          "rescheduledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "paymentNeeded": {
            "description": "Unpaid and still holding its place (#508)",
            "type": "boolean"
          },
          "reservedUntil": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "amountDue": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CustomerBookingAmount"
              }
            ],
            "nullable": true,
            "description": "What paying costs; a checkout of several bookings is paid as a whole"
          },
          "appointment": {
            "description": "An appointment (#924): with whom (first name, null when hidden)",
            "properties": {
              "with": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "spot": {
            "description": "The assigned spot label (#496)",
            "type": "string",
            "nullable": true
          },
          "checkIn": {
            "description": "Confirmed bookings: shown as a QR code and a short code at the door (#458)",
            "properties": {
              "code": {
                "type": "string"
              },
              "qr": {
                "type": "string"
              }
            },
            "type": "object",
            "nullable": true
          }
        },
        "type": "object"
      },
      "CustomerBookingDetail": {
        "description": "A booking with the coaches' first names and the practical notes of the activity and location (#704)",
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/CustomerBooking"
          },
          {
            "properties": {
              "coachNames": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "goodToKnow": {
                "description": "\"Good to know\" notes of the activity and location",
                "type": "string",
                "nullable": true
              }
            },
            "type": "object"
          }
        ]
      },
      "CustomerBookingRescheduleOption": {
        "description": "A session the booking can be moved to: same activity, same price, a free seat",
        "properties": {
          "sessionId": {
            "type": "string",
            "format": "uuid"
          },
          "startTime": {
            "type": "string",
            "format": "date-time"
          },
          "endTime": {
            "type": "string",
            "format": "date-time"
          },
          "activity": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object"
          },
          "location": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object"
          },
          "seriesName": {
            "type": "string",
            "nullable": true
          },
          "coachNames": {
            "description": "First names",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "availableSpots": {
            "type": "integer"
          },
          "price": {
            "$ref": "#/components/schemas/CustomerBookingAmount"
          },
          "samePrice": {
            "type": "boolean"
          },
          "alreadyBooked": {
            "type": "boolean"
          },
          "bookableOnItsOwn": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "CustomerAbsence": {
        "description": "A reported absence of a participant from one session of a series booking (#337)",
        "properties": {
          "id": {
            "type": "string"
          },
          "sessionId": {
            "type": "string",
            "format": "uuid"
          },
          "participantId": {
            "type": "string",
            "format": "uuid"
          },
          "bookingId": {
            "type": "string",
            "format": "uuid"
          },
          "reportedBy": {
            "description": "customer or staff",
            "type": "string"
          },
          "reason": {
            "type": "string",
            "nullable": true
          },
          "reportedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "CustomerAbsenceMakeUpCredit": {
        "description": "A make-up credit: one free session of the activity instead of a missed one (#337, #374)",
        "properties": {
          "id": {
            "type": "string"
          },
          "entitlementId": {
            "type": "string"
          },
          "participantId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "activityId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "missedSessionId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "validUntil": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "usedBookingId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "usedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "CustomerAbsenceSession": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "startTime": {
            "type": "string",
            "format": "date-time"
          },
          "endTime": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string"
          },
          "absence": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CustomerAbsence"
              }
            ],
            "nullable": true
          },
          "makeUpCredit": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CustomerAbsenceMakeUpCredit"
              }
            ],
            "nullable": true
          },
          "canReport": {
            "type": "boolean"
          },
          "canWithdraw": {
            "description": "Reported, before the deadline, and its make-up credit not used",
            "type": "boolean"
          },
          "deadline": {
            "description": "Until when the customer may report or withdraw",
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "CustomerWaitlistEntry": {
        "description": "A place on a waiting list: one of the customer's participants, or the customer's party (#796)",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "enum": [
              "session",
              "series"
            ]
          },
          "sessionId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "seriesId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "status": {
            "description": "notified: a spot is kept until expiresAt",
            "type": "string",
            "enum": [
              "waiting",
              "notified"
            ]
          },
          "origin": {
            "description": "reenrolment: a seat of the next term kept for a participant of this one (#736)",
            "type": "string",
            "enum": [
              "waitlist",
              "reenrolment"
            ]
          },
          "place": {
            "description": "Place in line",
            "type": "integer"
          },
          "participantFirstName": {
            "description": "Null for a party",
            "type": "string",
            "nullable": true
          },
          "partySummary": {
            "description": "A party: its tickets, e.g. \"2 × Adult, 1 × Child\"",
            "type": "string",
            "nullable": true
          },
          "seats": {
            "type": "integer"
          },
          "activityName": {
            "type": "string",
            "nullable": true
          },
          "seriesName": {
            "type": "string",
            "nullable": true
          },
          "locationName": {
            "type": "string",
            "nullable": true
          },
          "startsAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "endsAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "expiresAt": {
            "description": "Offered a spot: book it before this moment",
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "CustomerInterestRequest": {
        "description": "A pending \"notify me when I can book\" request (#511, #565)",
        "properties": {
          "id": {
            "type": "string"
          },
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "activityName": {
            "type": "string",
            "nullable": true
          },
          "sessionId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "seriesId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "seriesName": {
            "type": "string",
            "nullable": true
          },
          "startsAt": {
            "description": "A session: when it starts; a series: its first day",
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "CustomerReview": {
        "description": "A review the customer wrote (#355)",
        "properties": {
          "id": {
            "type": "string"
          },
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "activityName": {
            "type": "string",
            "nullable": true
          },
          "coach": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "rating": {
            "type": "integer",
            "maximum": 5,
            "minimum": 1
          },
          "comment": {
            "type": "string",
            "nullable": true
          },
          "reply": {
            "description": "The organisation's reply",
            "type": "string",
            "nullable": true
          },
          "repliedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "published",
              "hidden"
            ]
          }
        },
        "type": "object"
      },
      "CustomerReviewPrompt": {
        "description": "An activity the customer attended, to review (or whose review of this term to change)",
        "properties": {
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "activityName": {
            "type": "string"
          },
          "lastAttendedAt": {
            "type": "string",
            "format": "date-time"
          },
          "coaches": {
            "type": "array",
            "items": {
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                }
              },
              "type": "object"
            }
          },
          "review": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CustomerReview"
              }
            ],
            "nullable": true,
            "description": "This term's review, if any"
          }
        },
        "type": "object"
      },
      "CustomerAmount": {
        "properties": {
          "amount": {
            "description": "Minor units (cents)",
            "type": "integer"
          },
          "currency": {
            "type": "string",
            "example": "EUR"
          }
        },
        "type": "object"
      },
      "CustomerEntitlement": {
        "description": "A make-up credit, subscription, pass or series perk held by the customer or one of their participants",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "description": "The plan's name in the customer's language when translated, else the name it was sold under",
            "type": "string"
          },
          "sourceType": {
            "type": "string",
            "enum": [
              "absence",
              "subscription",
              "pass",
              "series_perk",
              "manual"
            ]
          },
          "sourceId": {
            "type": "string",
            "nullable": true
          },
          "customerId": {
            "description": "Set for a pass (shared by all the customer's participants)",
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "participantId": {
            "description": "Set when bound to one participant",
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "participantName": {
            "type": "string",
            "nullable": true
          },
          "services": {
            "type": "array",
            "items": {
              "properties": {
                "activityId": {
                  "type": "string",
                  "format": "uuid"
                },
                "activityName": {
                  "type": "string",
                  "nullable": true
                },
                "rule": {
                  "description": "unlimited, limited(count, per week|month|validity) or credits(cost)",
                  "type": "object"
                }
              },
              "type": "object"
            }
          },
          "credits": {
            "description": "Credits of a pass",
            "type": "integer",
            "nullable": true
          },
          "balance": {
            "description": "Credits left; null when unlimited",
            "type": "integer",
            "nullable": true
          },
          "used": {
            "description": "Active bookings made with it",
            "type": "integer"
          },
          "validFrom": {
            "type": "string",
            "format": "date-time"
          },
          "validUntil": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "cancelled"
            ]
          },
          "expired": {
            "type": "boolean"
          },
          "usable": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "cancelledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "cancellationReason": {
            "type": "string",
            "nullable": true
          },
          "receipt": {
            "description": "Bought online and paid: the receipt at /plan-purchases/{purchaseId}/receipt.pdf",
            "properties": {
              "purchaseId": {
                "type": "string",
                "format": "uuid"
              }
            },
            "type": "object",
            "nullable": true
          },
          "ledger": {
            "description": "Usage history, newest first",
            "type": "array",
            "items": {
              "properties": {
                "id": {
                  "type": "string"
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "booking",
                    "adjustment"
                  ]
                },
                "units": {
                  "type": "integer"
                },
                "bookingId": {
                  "type": "string",
                  "format": "uuid",
                  "nullable": true
                },
                "sessionStartsAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                },
                "activityName": {
                  "type": "string",
                  "nullable": true
                },
                "participantName": {
                  "type": "string",
                  "nullable": true
                },
                "createdAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "reversedAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                }
              },
              "type": "object"
            }
          },
          "participants": {
            "description": "Only with ?sessionId=: per participant whether it covers that session",
            "type": "array",
            "items": {
              "properties": {
                "participantId": {
                  "type": "string",
                  "format": "uuid"
                },
                "participantName": {
                  "type": "string"
                },
                "covered": {
                  "type": "boolean"
                },
                "reason": {
                  "type": "string",
                  "nullable": true,
                  "enum": [
                    "not_active",
                    "not_started",
                    "expired",
                    "other_participant",
                    "service_not_covered",
                    "not_individually_bookable",
                    "session_excluded",
                    "session_started",
                    "limit_reached",
                    "no_credits_left",
                    "already_used"
                  ]
                },
                "units": {
                  "type": "integer",
                  "nullable": true
                },
                "rule": {
                  "type": "object",
                  "nullable": true
                },
                "used": {
                  "type": "integer",
                  "nullable": true
                },
                "balance": {
                  "type": "integer",
                  "nullable": true
                }
              },
              "type": "object"
            }
          }
        },
        "type": "object"
      },
      "CustomerPlanPurchase": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "planId": {
            "type": "string",
            "format": "uuid"
          },
          "planName": {
            "type": "string"
          },
          "participantId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "startDate": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "paid",
              "cancelled"
            ]
          },
          "listPrice": {
            "$ref": "#/components/schemas/CustomerAmount"
          },
          "discount": {
            "$ref": "#/components/schemas/CustomerAmount"
          },
          "giftCards": {
            "$ref": "#/components/schemas/CustomerAmount"
          },
          "amountDue": {
            "$ref": "#/components/schemas/CustomerAmount"
          },
          "refunded": {
            "$ref": "#/components/schemas/CustomerAmount"
          },
          "offlinePaymentMethod": {
            "type": "string",
            "nullable": true,
            "enum": [
              "on_site",
              "bank_transfer"
            ]
          },
          "paymentDueAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "paidMethod": {
            "type": "string",
            "nullable": true
          },
          "entitlementId": {
            "description": "The pass or subscription granted once paid",
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "paidAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "cancelledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "CustomerPlanPurchaseOfflinePayment": {
        "description": "How to pay a plan bought to pay on site or by bank transfer (#607); granted once staff record the payment",
        "properties": {
          "method": {
            "type": "string",
            "enum": [
              "on_site",
              "bank_transfer"
            ]
          },
          "reference": {
            "description": "Structured payment reference",
            "type": "string",
            "nullable": true
          },
          "dueAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "iban": {
            "description": "Bank transfer only",
            "type": "string",
            "nullable": true
          },
          "bic": {
            "type": "string",
            "nullable": true
          },
          "accountHolder": {
            "type": "string",
            "nullable": true
          },
          "amount": {
            "$ref": "#/components/schemas/CustomerAmount"
          }
        },
        "type": "object"
      },
      "CustomerPlanPurchaseQuote": {
        "properties": {
          "price": {
            "$ref": "#/components/schemas/CustomerAmount"
          },
          "discount": {
            "$ref": "#/components/schemas/CustomerAmount"
          },
          "giftCards": {
            "description": "Typed gift cards and what each one pays",
            "type": "array",
            "items": {
              "properties": {
                "code": {
                  "type": "string"
                },
                "amount": {
                  "$ref": "#/components/schemas/CustomerAmount"
                }
              },
              "type": "object"
            }
          },
          "giftCardTotal": {
            "$ref": "#/components/schemas/CustomerAmount"
          },
          "accountCreditTotal": {
            "$ref": "#/components/schemas/CustomerAmount"
          },
          "due": {
            "$ref": "#/components/schemas/CustomerAmount"
          }
        },
        "type": "object"
      },
      "CustomerPlanPurchaseInput": {
        "required": [
          "planId"
        ],
        "properties": {
          "planId": {
            "type": "string",
            "format": "uuid"
          },
          "participantId": {
            "description": "The customer's participant a subscription is bound to",
            "type": "string",
            "format": "uuid"
          },
          "startDate": {
            "description": "When the plan starts (local date), where the plan allows choosing",
            "type": "string",
            "format": "date"
          },
          "discountCode": {
            "type": "string"
          },
          "giftCardCodes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "useAccountCredit": {
            "description": "Spend the customer's account credit (#752) after the typed gift cards",
            "type": "boolean",
            "default": false
          },
          "paymentMethod": {
            "type": "string",
            "default": "online",
            "enum": [
              "online",
              "on_site",
              "bank_transfer"
            ]
          },
          "returnUrl": {
            "description": "Page to come back to after paying; kept to pages the organisation allows, else the booking site's purchase return page",
            "type": "string"
          }
        },
        "type": "object"
      },
      "CustomerCredit": {
        "description": "Account credit staff issued to the customer (#752); spent at checkout with useAccountCredit, never by code",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "initialAmount": {
            "$ref": "#/components/schemas/CustomerAmount"
          },
          "balance": {
            "$ref": "#/components/schemas/CustomerAmount"
          },
          "usable": {
            "type": "boolean"
          },
          "expiresAt": {
            "description": "null: no expiry",
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "issuedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "CustomerCharge": {
        "description": "An amount the customer owes: a late-cancellation or no-show fee (#406), or a venue hire deposit, balance or extra (#794)",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "late_cancellation",
              "no_show",
              "hire_deposit",
              "hire_balance",
              "hire_extra"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "paid",
              "waived",
              "cancelled"
            ]
          },
          "amount": {
            "$ref": "#/components/schemas/CustomerAmount"
          },
          "bookingId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "hireId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "lines": {
            "description": "Lines of an extra hire charge",
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "refunded": {
            "$ref": "#/components/schemas/CustomerAmount"
          },
          "refundedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "sessionId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "activityName": {
            "type": "string",
            "nullable": true
          },
          "eventStart": {
            "type": "string",
            "format": "date-time"
          },
          "dueAt": {
            "type": "string",
            "format": "date-time"
          },
          "overdue": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "paidAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "waivedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "cancelledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "CustomerHire": {
        "description": "A venue hire request of the customer (#794): the party, the quote once sent, open amounts, how to pay and the cancellation deadline. Never the staff note, tax categories or answers.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "referenceCode": {
            "type": "string"
          },
          "reference": {
            "description": "Reference shown to the customer",
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "new",
              "quoted",
              "accepted",
              "confirmed",
              "declined",
              "expired",
              "cancelled"
            ]
          },
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "locationId": {
            "type": "string",
            "format": "uuid"
          },
          "dayPartId": {
            "type": "string",
            "format": "uuid"
          },
          "activity": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object"
          },
          "location": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object"
          },
          "dayPart": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "startsAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "endsAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "spaces": {
            "description": "Empty while the request is new",
            "type": "array",
            "items": {
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string",
                  "nullable": true
                }
              },
              "type": "object"
            }
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "startsAt": {
            "type": "string",
            "format": "date-time"
          },
          "endsAt": {
            "type": "string",
            "format": "date-time"
          },
          "tickets": {
            "type": "array",
            "items": {
              "properties": {
                "ticketTypeId": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "quantity": {
                  "type": "integer"
                },
                "seatWeight": {
                  "type": "integer"
                }
              },
              "type": "object"
            }
          },
          "extras": {
            "type": "array",
            "items": {
              "properties": {
                "extraId": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "unit": {
                  "type": "string"
                },
                "quantity": {
                  "type": "integer"
                }
              },
              "type": "object"
            }
          },
          "partySize": {
            "type": "integer"
          },
          "seats": {
            "type": "integer"
          },
          "contact": {
            "properties": {
              "firstName": {
                "type": "string"
              },
              "lastName": {
                "type": "string"
              },
              "email": {
                "type": "string"
              },
              "phone": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object"
          },
          "billingDetails": {
            "properties": {
              "companyName": {
                "type": "string"
              },
              "vatNumber": {
                "type": "string",
                "nullable": true
              },
              "street": {
                "type": "string"
              },
              "postcode": {
                "type": "string"
              },
              "town": {
                "type": "string"
              },
              "country": {
                "type": "string"
              }
            },
            "type": "object",
            "nullable": true
          },
          "remarks": {
            "type": "string",
            "nullable": true
          },
          "currency": {
            "type": "string"
          },
          "quote": {
            "description": "Once a quote was sent",
            "properties": {
              "lines": {
                "type": "array",
                "items": {
                  "properties": {
                    "type": {
                      "type": "string"
                    },
                    "itemId": {
                      "type": "string",
                      "nullable": true
                    },
                    "name": {
                      "type": "string"
                    },
                    "quantity": {
                      "type": "integer"
                    },
                    "unitPrice": {
                      "$ref": "#/components/schemas/CustomerAmount"
                    },
                    "productCode": {
                      "type": "string",
                      "nullable": true
                    },
                    "total": {
                      "$ref": "#/components/schemas/CustomerAmount"
                    }
                  },
                  "type": "object"
                }
              },
              "total": {
                "$ref": "#/components/schemas/CustomerAmount"
              },
              "deposit": {
                "$ref": "#/components/schemas/CustomerAmount"
              },
              "balance": {
                "$ref": "#/components/schemas/CustomerAmount"
              },
              "validUntil": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "sentAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "paymentMethod": {
            "type": "string",
            "nullable": true,
            "enum": [
              "online",
              "bank_transfer",
              "on_site"
            ]
          },
          "paymentReference": {
            "type": "string",
            "nullable": true
          },
          "acceptedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "acceptedByStaff": {
            "type": "boolean"
          },
          "confirmedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "declinedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "declineReason": {
            "type": "string",
            "nullable": true
          },
          "expiredAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "cancelledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "cancelledBy": {
            "type": "string",
            "nullable": true,
            "enum": [
              "customer",
              "staff"
            ]
          },
          "cancelReason": {
            "type": "string",
            "nullable": true
          },
          "depositKept": {
            "type": "boolean",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "charges": {
            "description": "Deposit, balance and extras (cancelled ones left out); pay an open one with POST /charges/{id}/pay",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CustomerCharge"
            }
          },
          "canAccept": {
            "type": "boolean"
          },
          "canDecline": {
            "type": "boolean"
          },
          "paymentOptions": {
            "description": "How open amounts can be paid while the hire is open",
            "properties": {
              "online": {
                "type": "boolean"
              },
              "bankTransfer": {
                "type": "boolean"
              },
              "onSite": {
                "type": "boolean"
              }
            },
            "type": "object",
            "nullable": true
          },
          "bankTransfer": {
            "description": "Paid by bank transfer and still open",
            "properties": {
              "iban": {
                "type": "string",
                "nullable": true
              },
              "bic": {
                "type": "string",
                "nullable": true
              },
              "accountHolder": {
                "type": "string",
                "nullable": true
              },
              "reference": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "cancellation": {
            "properties": {
              "deadline": {
                "description": "Before it the deposit is refunded, after it kept",
                "type": "string",
                "format": "date-time"
              },
              "depositKeptNow": {
                "type": "boolean"
              },
              "customerMayCancel": {
                "type": "boolean"
              },
              "depositPaid": {
                "type": "boolean"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "CustomerHireError": {
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "reason": {
            "description": "space_conflict only",
            "type": "string"
          },
          "conflicts": {
            "description": "space_conflict only",
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "details": {
            "type": "object"
          }
        },
        "type": "object"
      },
      "StaffBillingDetails": {
        "description": "Company billing details (#791); the VAT number is checked on format per EU country",
        "required": [
          "companyName",
          "street",
          "postcode",
          "town",
          "country"
        ],
        "properties": {
          "companyName": {
            "type": "string"
          },
          "vatNumber": {
            "type": "string",
            "nullable": true
          },
          "street": {
            "type": "string"
          },
          "postcode": {
            "type": "string"
          },
          "town": {
            "type": "string"
          },
          "country": {
            "description": "ISO 3166-1 alpha-2",
            "type": "string",
            "example": "BE"
          }
        },
        "type": "object"
      },
      "StaffCustomerTagRef": {
        "description": "A manual customer tag (#758)",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "colour": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "StaffCustomerBasic": {
        "description": "A customer as returned after creating or updating",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "fullName": {
            "type": "string"
          },
          "nationalRegistryNumber": {
            "type": "string",
            "nullable": true
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "registered",
              "active",
              "deactivated"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "billingDetails": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffBillingDetails"
              }
            ],
            "nullable": true,
            "description": "Only in the update response"
          }
        },
        "type": "object"
      },
      "StaffCustomer": {
        "description": "A customer as staff see it. With the assigned scope nationalRegistryNumber and billingDetails are null.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "fullName": {
            "type": "string"
          },
          "nationalRegistryNumber": {
            "type": "string",
            "nullable": true
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "registered",
              "active",
              "deactivated"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "emailVerified": {
            "type": "boolean"
          },
          "emailProblem": {
            "type": "string",
            "nullable": true,
            "enum": [
              "bounced",
              "complained"
            ]
          },
          "emailBouncedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "deletionRequestedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "anonymisedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "language": {
            "type": "string",
            "nullable": true
          },
          "billingDetails": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffBillingDetails"
              }
            ],
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffCustomerDetail": {
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/StaffCustomer"
          },
          {
            "properties": {
              "tags": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/StaffCustomerTagRef"
                }
              },
              "participants": {
                "description": "The participants they manage; with the assigned scope only my attendees",
                "type": "array",
                "items": {
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "firstName": {
                      "type": "string"
                    },
                    "lastName": {
                      "type": "string"
                    },
                    "fullName": {
                      "type": "string"
                    },
                    "dateOfBirth": {
                      "type": "string",
                      "format": "date",
                      "nullable": true
                    }
                  },
                  "type": "object"
                }
              }
            },
            "type": "object"
          }
        ]
      },
      "StaffCustomerListItem": {
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/StaffCustomer"
          },
          {
            "properties": {
              "participantCount": {
                "type": "integer"
              },
              "lastBookingAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "nextBookingAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "plans": {
                "description": "Active plans; null without entitlement:read",
                "type": "array",
                "items": {
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "type": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "validUntil": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    },
                    "creditsLeft": {
                      "type": "integer",
                      "nullable": true
                    }
                  },
                  "type": "object"
                },
                "nullable": true
              },
              "openFees": {
                "description": "Null without payment:read",
                "properties": {
                  "count": {
                    "type": "integer"
                  },
                  "totals": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/StaffAmount"
                    }
                  }
                },
                "type": "object",
                "nullable": true
              },
              "outstanding": {
                "description": "What the customer owes per currency (bookings, plans to pay later, fees); null without payment:read",
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/StaffAmount"
                },
                "nullable": true
              },
              "tags": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/StaffCustomerTagRef"
                }
              }
            },
            "type": "object"
          }
        ]
      },
      "StaffCustomerToCheck": {
        "description": "A customer whose email bounced or is still unverified 48 h after booking something upcoming (#425)",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "fullName": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "reason": {
            "type": "string",
            "enum": [
              "bounced",
              "complained",
              "unverified"
            ]
          },
          "emailBouncedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "nextBookingAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "nextBookingActivity": {
            "type": "string",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffCustomerTimelineEntry": {
        "description": "One event in a customer's history (#605)",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "booking_created",
              "booking_cancelled",
              "payment_received",
              "payment_refunded",
              "plan_purchased",
              "plan_refunded",
              "entitlement_used",
              "waitlist_joined",
              "waitlist_offered",
              "check_in",
              "email_sent",
              "note",
              "fee_charged"
            ]
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time"
          },
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "detail": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          },
          "participantName": {
            "type": "string",
            "nullable": true
          },
          "startsAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "bookingId": {
            "type": "string",
            "nullable": true
          },
          "amount": {
            "description": "Cents",
            "type": "integer",
            "nullable": true
          },
          "currency": {
            "type": "string",
            "nullable": true
          },
          "units": {
            "type": "integer",
            "nullable": true
          },
          "actor": {
            "type": "string",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffCustomerTask": {
        "properties": {
          "id": {
            "type": "string"
          },
          "customerId": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "dueDate": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "assigneeUserId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "assigneeName": {
            "type": "string",
            "nullable": true
          },
          "done": {
            "type": "boolean"
          },
          "completedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "StaffAccountCredit": {
        "description": "Account credit issued to the customer (#752): a gift card only that customer can spend",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "active",
              "depleted",
              "expired"
            ]
          },
          "usable": {
            "type": "boolean"
          },
          "initialAmount": {
            "$ref": "#/components/schemas/StaffAmount"
          },
          "balance": {
            "$ref": "#/components/schemas/StaffAmount"
          },
          "reason": {
            "type": "string",
            "nullable": true
          },
          "bookingId": {
            "description": "The booking whose refund it replaced",
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "expiresAt": {
            "description": "Null: no expiry",
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "issuedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "DiscountValidation": {
        "required": [
          "valid"
        ],
        "properties": {
          "valid": {
            "type": "boolean"
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "discountCode": {
            "type": "object",
            "nullable": true
          },
          "discountAmount": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "nullable": true
          },
          "discountedPrice": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "nullable": true
          }
        },
        "type": "object"
      },
      "ErrorResponse": {
        "description": "Standard error response",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "description": "Error message",
            "type": "string",
            "example": "Resource not found"
          }
        },
        "type": "object"
      },
      "StaffExtra": {
        "description": "An extra sold on top of a headcount booking (#793)",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "price": {
            "$ref": "#/components/schemas/StaffAmount"
          },
          "unit": {
            "description": "per_person: × the party's seats; per_piece: quantity picked; flat: once",
            "type": "string",
            "enum": [
              "per_person",
              "per_piece",
              "flat"
            ]
          },
          "maxQuantity": {
            "description": "per_piece only",
            "type": "integer",
            "nullable": true
          },
          "taxCategoryId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "productCode": {
            "description": "Accounting product code",
            "type": "string",
            "nullable": true
          },
          "refundableOnLateCancel": {
            "type": "boolean"
          },
          "active": {
            "type": "boolean"
          },
          "sortOrder": {
            "type": "integer"
          },
          "locationIds": {
            "description": "Empty: every location",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "imageUrl": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "archived": {
            "type": "boolean"
          },
          "archivedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "translations": {
            "description": "Name and description in other languages (#705): {\"fr\": {\"name\": \"…\"}}",
            "type": "object"
          }
        },
        "type": "object"
      },
      "StaffExtraInput": {
        "description": "The whole extra: fields left out take their default",
        "required": [
          "name",
          "price",
          "unit"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "price": {
            "required": [
              "amount"
            ],
            "properties": {
              "amount": {
                "description": "Cents",
                "type": "integer"
              },
              "currency": {
                "description": "Default the organisation's",
                "type": "string"
              }
            },
            "type": "object"
          },
          "unit": {
            "type": "string",
            "enum": [
              "per_person",
              "per_piece",
              "flat"
            ]
          },
          "maxQuantity": {
            "type": "integer",
            "nullable": true
          },
          "taxCategoryId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "productCode": {
            "type": "string",
            "nullable": true
          },
          "refundableOnLateCancel": {
            "type": "boolean",
            "default": true
          },
          "active": {
            "type": "boolean",
            "default": true
          },
          "locationIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "sortOrder": {
            "type": "integer",
            "nullable": true
          },
          "translations": {
            "description": "Left out: unchanged; {} clears them",
            "type": "object"
          }
        },
        "type": "object"
      },
      "StaffValueAmount": {
        "description": "An amount in cents as value + currency",
        "properties": {
          "value": {
            "type": "integer",
            "example": 1000
          },
          "currency": {
            "type": "string",
            "example": "EUR"
          }
        },
        "type": "object"
      },
      "StaffGiftCard": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "organisationId": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "active",
              "depleted",
              "expired"
            ]
          },
          "initialAmount": {
            "$ref": "#/components/schemas/StaffBookingStringAmount"
          },
          "currentBalance": {
            "$ref": "#/components/schemas/StaffBookingStringAmount"
          },
          "taxCategoryId": {
            "type": "string",
            "nullable": true
          },
          "taxRate": {
            "description": "Basis points",
            "type": "integer",
            "nullable": true
          },
          "recipientEmail": {
            "type": "string",
            "nullable": true
          },
          "recipientName": {
            "type": "string",
            "nullable": true
          },
          "personalMessage": {
            "type": "string",
            "nullable": true
          },
          "purchasedById": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "adminNote": {
            "type": "string",
            "nullable": true
          },
          "customerId": {
            "description": "Account credit (#752): only this customer can spend it",
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "accountCredit": {
            "type": "boolean"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffDiscountCode": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": "string",
            "example": "EARLY"
          },
          "description": {
            "description": "Shown to customers for an automatic code",
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "percentage",
              "fixed_amount"
            ]
          },
          "applicability": {
            "type": "string",
            "enum": [
              "all",
              "sessions_only",
              "series_only"
            ]
          },
          "isActive": {
            "type": "boolean"
          },
          "currentUses": {
            "type": "integer"
          },
          "maxUses": {
            "type": "integer",
            "nullable": true
          },
          "maxUsesPerCustomer": {
            "type": "integer",
            "nullable": true
          },
          "validFrom": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "validUntil": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "automatic": {
            "description": "Applied to customer checkouts without typing",
            "type": "boolean"
          },
          "itemScope": {
            "type": "string",
            "enum": [
              "all",
              "after_first"
            ]
          },
          "conditions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffDiscountCondition"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "percentageValue": {
            "description": "Percentage codes only",
            "type": "integer",
            "maximum": 100,
            "minimum": 1
          },
          "fixedAmount": {
            "$ref": "#/components/schemas/StaffValueAmount"
          },
          "minimumOrderAmount": {
            "$ref": "#/components/schemas/StaffValueAmount"
          }
        },
        "type": "object"
      },
      "StaffDiscountCondition": {
        "description": "A condition that must hold; today only {kind: min_items, count: 2..100}: at least so many bookings in one order",
        "required": [
          "kind"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "min_items"
            ]
          },
          "count": {
            "type": "integer",
            "maximum": 100,
            "minimum": 2
          }
        },
        "type": "object"
      },
      "StaffDiscountCodeInput": {
        "description": "On update code, type and the value cannot change; description and applicability are required on both",
        "required": [
          "description",
          "applicability"
        ],
        "properties": {
          "code": {
            "description": "Create only, unique in the organisation",
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "type": {
            "description": "Create only",
            "type": "string",
            "enum": [
              "percentage",
              "fixed_amount"
            ]
          },
          "applicability": {
            "type": "string",
            "enum": [
              "all",
              "sessions_only",
              "series_only"
            ]
          },
          "percentageValue": {
            "description": "Create only, required for percentage",
            "type": "integer",
            "maximum": 100,
            "minimum": 1
          },
          "fixedAmountValue": {
            "description": "Create only, cents, required for fixed_amount",
            "type": "integer"
          },
          "fixedAmountCurrency": {
            "description": "Create only, required for fixed_amount",
            "type": "string"
          },
          "minimumOrderAmountValue": {
            "type": "integer",
            "nullable": true
          },
          "minimumOrderAmountCurrency": {
            "type": "string",
            "nullable": true
          },
          "validFrom": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "validUntil": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "maxUses": {
            "type": "integer",
            "nullable": true
          },
          "maxUsesPerCustomer": {
            "type": "integer",
            "nullable": true
          },
          "isActive": {
            "description": "Update only",
            "type": "boolean"
          },
          "automatic": {
            "type": "boolean",
            "default": false
          },
          "itemScope": {
            "type": "string",
            "default": "all",
            "enum": [
              "all",
              "after_first"
            ]
          },
          "conditions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffDiscountCondition"
            }
          }
        },
        "type": "object"
      },
      "TaxCategory": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "example": "Standard"
          },
          "rate": {
            "description": "Basis points (2100 = 21%)",
            "type": "integer",
            "example": 2100,
            "maximum": 10000,
            "minimum": 0
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "isDefault": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "TaxCategoryInput": {
        "required": [
          "name",
          "rate"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "rate": {
            "description": "Basis points (2100 = 21%)",
            "type": "integer",
            "maximum": 10000,
            "minimum": 0
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "isDefault": {
            "type": "boolean",
            "default": false
          }
        },
        "type": "object"
      },
      "GiftCardOrder": {
        "required": [
          "orderId",
          "checkoutUrl"
        ],
        "properties": {
          "orderId": {
            "type": "string",
            "format": "uuid"
          },
          "checkoutUrl": {
            "type": "string",
            "format": "uri"
          }
        },
        "type": "object"
      },
      "StaffHireLine": {
        "description": "A quote or charge line",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "ticket",
              "extra",
              "space",
              "extra_hours",
              "below_minimum",
              "custom"
            ]
          },
          "itemId": {
            "description": "Ticket type, extra or space id",
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string"
          },
          "quantity": {
            "type": "integer"
          },
          "unitPrice": {
            "$ref": "#/components/schemas/StaffAmount"
          },
          "productCode": {
            "type": "string",
            "nullable": true
          },
          "total": {
            "$ref": "#/components/schemas/StaffAmount"
          },
          "taxCategoryId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffHireLineInput": {
        "required": [
          "name",
          "quantity",
          "unitPrice"
        ],
        "properties": {
          "type": {
            "type": "string",
            "default": "custom",
            "enum": [
              "ticket",
              "extra",
              "space",
              "extra_hours",
              "below_minimum",
              "custom"
            ]
          },
          "itemId": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "maxLength": 200
          },
          "quantity": {
            "type": "integer",
            "maximum": 10000,
            "minimum": 0
          },
          "unitPrice": {
            "required": [
              "amount"
            ],
            "properties": {
              "amount": {
                "description": "Cents",
                "type": "integer"
              },
              "currency": {
                "description": "Must be the hire's currency; default it",
                "type": "string"
              }
            },
            "type": "object"
          },
          "productCode": {
            "type": "string",
            "nullable": true
          },
          "taxCategoryId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffHireTicketInput": {
        "required": [
          "ticketTypeId",
          "quantity"
        ],
        "properties": {
          "ticketTypeId": {
            "type": "string",
            "format": "uuid"
          },
          "quantity": {
            "type": "integer"
          }
        },
        "type": "object"
      },
      "StaffHireSummary": {
        "description": "A hire in the requests inbox",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "reference": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "new",
              "quoted",
              "accepted",
              "confirmed",
              "declined",
              "expired",
              "cancelled"
            ]
          },
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "activityName": {
            "type": "string",
            "nullable": true
          },
          "locationId": {
            "type": "string",
            "format": "uuid"
          },
          "locationName": {
            "type": "string",
            "nullable": true
          },
          "dayPartName": {
            "type": "string",
            "nullable": true
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "startsAt": {
            "type": "string",
            "format": "date-time"
          },
          "endsAt": {
            "type": "string",
            "format": "date-time"
          },
          "partySize": {
            "type": "integer"
          },
          "seats": {
            "type": "integer"
          },
          "contactName": {
            "type": "string",
            "nullable": true
          },
          "companyName": {
            "type": "string",
            "nullable": true
          },
          "spaceIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "spaceNames": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "total": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffAmount"
              }
            ],
            "nullable": true,
            "description": "Once quoted"
          },
          "quoteValidUntil": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "deposit": {
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "open",
                  "paid",
                  "waived",
                  "cancelled"
                ]
              },
              "overdue": {
                "type": "boolean"
              },
              "amount": {
                "$ref": "#/components/schemas/StaffAmount"
              }
            },
            "type": "object",
            "nullable": true
          },
          "overMaximum": {
            "description": "The party exceeds the spaces' maxGuests",
            "type": "boolean"
          },
          "conflictCount": {
            "description": "Hires, blocks or booked sessions in the way of an open hire",
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "StaffHireCharge": {
        "description": "A deposit, balance or extra charge of a hire",
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/CustomerCharge"
          },
          {
            "properties": {
              "paidMethod": {
                "type": "string",
                "nullable": true
              },
              "onlinePayment": {
                "description": "Paid through the gateway",
                "type": "boolean"
              }
            },
            "type": "object"
          }
        ]
      },
      "StaffHire": {
        "description": "Everything staff see of one hire",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "referenceCode": {
            "type": "string"
          },
          "reference": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "new",
              "quoted",
              "accepted",
              "confirmed",
              "declined",
              "expired",
              "cancelled"
            ]
          },
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "locationId": {
            "type": "string",
            "format": "uuid"
          },
          "dayPartId": {
            "type": "string",
            "format": "uuid"
          },
          "customerId": {
            "type": "string",
            "format": "uuid"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "startsAt": {
            "type": "string",
            "format": "date-time"
          },
          "endsAt": {
            "type": "string",
            "format": "date-time"
          },
          "tickets": {
            "type": "array",
            "items": {
              "properties": {
                "ticketTypeId": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "quantity": {
                  "type": "integer"
                },
                "seatWeight": {
                  "type": "integer"
                }
              },
              "type": "object"
            }
          },
          "extras": {
            "type": "array",
            "items": {
              "properties": {
                "extraId": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "unit": {
                  "type": "string"
                },
                "quantity": {
                  "type": "integer"
                }
              },
              "type": "object"
            }
          },
          "partySize": {
            "type": "integer"
          },
          "seats": {
            "type": "integer"
          },
          "contact": {
            "description": "As typed: firstName, lastName, email, phone",
            "type": "object"
          },
          "billingDetails": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffBillingDetails"
              }
            ],
            "nullable": true
          },
          "answers": {
            "description": "Booking question name => answer",
            "type": "object"
          },
          "remarks": {
            "type": "string",
            "nullable": true
          },
          "internalNote": {
            "type": "string",
            "nullable": true
          },
          "spaceIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "currency": {
            "type": "string"
          },
          "quote": {
            "properties": {
              "lines": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/StaffHireLine"
                }
              },
              "total": {
                "$ref": "#/components/schemas/StaffAmount"
              },
              "deposit": {
                "$ref": "#/components/schemas/StaffAmount"
              },
              "balance": {
                "$ref": "#/components/schemas/StaffAmount"
              },
              "validUntil": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "sentAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "paymentMethod": {
            "type": "string",
            "nullable": true,
            "enum": [
              "online",
              "bank_transfer",
              "on_site"
            ]
          },
          "paymentReference": {
            "type": "string",
            "nullable": true
          },
          "acceptedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "acceptedByStaff": {
            "type": "boolean"
          },
          "confirmedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "declinedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "declineReason": {
            "type": "string",
            "nullable": true
          },
          "expiredAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "cancelledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "cancelledBy": {
            "type": "string",
            "nullable": true
          },
          "cancelReason": {
            "type": "string",
            "nullable": true
          },
          "depositKept": {
            "type": "boolean",
            "nullable": true
          },
          "attendance": {
            "type": "string",
            "enum": [
              "present",
              "absent",
              "unmarked"
            ]
          },
          "checkedInAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "activity": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object"
          },
          "location": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object"
          },
          "dayPart": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "startsAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "endsAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "spaces": {
            "type": "array",
            "items": {
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string",
                  "nullable": true
                }
              },
              "type": "object"
            }
          },
          "customer": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "email": {
                "type": "string"
              },
              "phone": {
                "type": "string",
                "nullable": true
              },
              "emailVerified": {
                "type": "boolean"
              }
            },
            "type": "object",
            "nullable": true
          },
          "charges": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffHireCharge"
            }
          },
          "owed": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffAmount"
              }
            ],
            "nullable": true,
            "description": "Deposit refund owed to the client (paid offline, cancelled in time)"
          },
          "cancellation": {
            "properties": {
              "deadline": {
                "description": "Before it the deposit is refunded, after it kept",
                "type": "string",
                "format": "date-time"
              },
              "depositKeptNow": {
                "type": "boolean"
              },
              "customerMayCancel": {
                "type": "boolean"
              },
              "depositPaid": {
                "type": "boolean"
              }
            },
            "type": "object"
          },
          "conflicts": {
            "description": "Hires, requests, blocks or booked sessions sharing the spaces then (open hires only)",
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "guestCapacity": {
            "properties": {
              "minGuests": {
                "type": "integer",
                "nullable": true
              },
              "maxGuests": {
                "type": "integer",
                "nullable": true
              },
              "partySize": {
                "type": "integer"
              },
              "seats": {
                "type": "integer"
              },
              "belowMinimum": {
                "type": "boolean"
              },
              "overMaximum": {
                "type": "boolean"
              }
            },
            "type": "object",
            "nullable": true
          },
          "canChangePrices": {
            "description": "The caller holds payment:write",
            "type": "boolean"
          },
          "paymentOptions": {
            "properties": {
              "online": {
                "type": "boolean"
              },
              "bankTransfer": {
                "type": "boolean"
              },
              "onSite": {
                "type": "boolean"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "StaffHireResponse": {
        "properties": {
          "hire": {
            "$ref": "#/components/schemas/StaffHire"
          }
        },
        "type": "object"
      },
      "StaffHireRefund": {
        "properties": {
          "chargeId": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "hire_deposit",
              "hire_balance",
              "hire_extra"
            ]
          },
          "amount": {
            "$ref": "#/components/schemas/StaffAmount"
          },
          "method": {
            "description": "online: through the gateway; owed: paid offline, to pay back by hand; manual: recorded as refunded by hand",
            "type": "string",
            "enum": [
              "online",
              "owed",
              "manual"
            ]
          }
        },
        "type": "object"
      },
      "StaffHireError": {
        "description": "A refused hire action. code: not_found, not_requestable, unknown_day_part, day_part_closed, outside_day_part, invalid_date, invalid_time, invalid_status, quote_expired, invalid_quote, invalid_line, invalid_refund, invalid_reason, invalid_spaces, payment_method_unavailable, payment_write_required, cancellation_closed, space_conflict, a party booking code, billing_details_required, invalid_billing_details, answers_required",
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "reason": {
            "description": "space_conflict only: space_occupied, sessions_have_bookings, appointments_booked",
            "type": "string"
          },
          "conflicts": {
            "description": "space_conflict only",
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "details": {
            "type": "object"
          }
        },
        "type": "object"
      },
      "Image": {
        "required": [
          "id",
          "url"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "alt": {
            "type": "string",
            "nullable": true
          },
          "position": {
            "type": "integer",
            "example": 0
          }
        },
        "type": "object"
      },
      "ImportJobSummary": {
        "properties": {
          "id": {
            "type": "string"
          },
          "fileName": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "uploaded",
              "queued",
              "running",
              "completed",
              "failed"
            ]
          },
          "rowCount": {
            "type": "integer"
          },
          "reportIsDryRun": {
            "description": "Whether `report` comes from a dry run",
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "ImportJob": {
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/ImportJobSummary"
          },
          {
            "properties": {
              "headers": {
                "description": "The file's column headers",
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "fields": {
                "description": "The fields columns can be mapped to",
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "suggestedMapping": {
                "description": "Field => column guessed from the headers",
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "preview": {
                "description": "The first 5 rows",
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              },
              "options": {
                "description": "The mapping and options of the last (dry) run",
                "type": "object",
                "nullable": true
              },
              "report": {
                "description": "What was (or would be) created, updated and skipped, with the problems per row",
                "type": "object",
                "nullable": true
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            },
            "type": "object"
          }
        ]
      },
      "ImportRunInput": {
        "required": [
          "mapping"
        ],
        "properties": {
          "mapping": {
            "description": "Field => CSV column (null or empty: not imported). Email and a first name or full name are required; bookings need the activity field, a session time also the session date",
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "nullable": true
            }
          },
          "dateOrder": {
            "type": "string",
            "default": "dmy",
            "enum": [
              "dmy",
              "mdy"
            ]
          },
          "timezone": {
            "description": "Of dates and times in the file; default the organisation's",
            "type": "string",
            "example": "Europe/Brussels",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffLocationAddress": {
        "properties": {
          "addressLine1": {
            "type": "string",
            "example": "Kerkstraat 1"
          },
          "addressLine2": {
            "type": "string",
            "nullable": true
          },
          "city": {
            "type": "string",
            "example": "Gent"
          },
          "postalCode": {
            "type": "string",
            "example": "9000"
          },
          "country": {
            "type": "string",
            "example": "BE"
          },
          "full": {
            "description": "The address on one line (read only)",
            "type": "string"
          }
        },
        "type": "object"
      },
      "StaffLocation": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "address": {
            "$ref": "#/components/schemas/StaffLocationAddress"
          },
          "paymentGatewayId": {
            "description": "Its own payment gateway; null: the organisation's",
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "currency": {
            "description": "Its own currency (#350); null: the organisation's",
            "type": "string",
            "nullable": true
          },
          "emailInfo": {
            "description": "\"Good to know\" notes in confirmation and reminder emails (#704)",
            "type": "string",
            "nullable": true
          },
          "translations": {
            "description": "Other languages (#705): {\"fr\": {\"name\": \"…\", \"emailInfo\": \"…\"}}",
            "type": "object"
          },
          "effectivePaymentGateway": {
            "description": "The gateway online payments here go through (#783): its own, else the organisation's first active one that takes its currency",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "type": {
                "type": "string",
                "example": "mollie"
              },
              "inherited": {
                "description": "True when it is the organisation's",
                "type": "boolean"
              }
            },
            "type": "object",
            "nullable": true
          },
          "images": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffActivityImage"
            }
          },
          "primaryImageId": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffLocationResponse": {
        "properties": {
          "location": {
            "$ref": "#/components/schemas/StaffLocation"
          }
        },
        "type": "object"
      },
      "StaffLocationInput": {
        "required": [
          "name",
          "address"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "address": {
            "required": [
              "addressLine1",
              "city",
              "postalCode",
              "country"
            ],
            "properties": {
              "addressLine1": {
                "type": "string"
              },
              "addressLine2": {
                "type": "string",
                "nullable": true
              },
              "city": {
                "type": "string"
              },
              "postalCode": {
                "type": "string"
              },
              "country": {
                "type": "string"
              }
            },
            "type": "object"
          },
          "paymentGatewayId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "currency": {
            "description": "ISO 4217 code the gateway takes; on update left out: unchanged, null: the organisation's",
            "type": "string",
            "nullable": true
          },
          "emailInfo": {
            "description": "On update left out: unchanged, null or empty: none",
            "type": "string",
            "nullable": true
          },
          "translations": {
            "description": "Left out: unchanged; {} clears them",
            "type": "object"
          }
        },
        "type": "object"
      },
      "StaffSpaceHirePricing": {
        "description": "What hiring the space costs (#794), in one currency",
        "properties": {
          "currency": {
            "description": "In the input optional: the location's or organisation's currency",
            "type": "string",
            "example": "EUR"
          },
          "dayPartFees": {
            "type": "array",
            "items": {
              "properties": {
                "dayPartId": {
                  "type": "string",
                  "format": "uuid"
                },
                "amount": {
                  "description": "Cents",
                  "type": "integer"
                }
              },
              "type": "object"
            }
          },
          "extraHourFee": {
            "description": "Cents per hour beyond the day part",
            "type": "integer",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffSpace": {
        "description": "A room at a location (#792); a combination (\"Both salons\") lists its parts",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "locationId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "minGuests": {
            "type": "integer",
            "nullable": true
          },
          "maxGuests": {
            "type": "integer",
            "nullable": true
          },
          "partIds": {
            "description": "Spaces a combination is made of (plain spaces only)",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "isCombination": {
            "type": "boolean"
          },
          "sortOrder": {
            "type": "integer"
          },
          "active": {
            "type": "boolean"
          },
          "hirePricing": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffSpaceHirePricing"
              }
            ],
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "translations": {
            "description": "{\"fr\": {\"name\": \"…\", \"description\": \"…\"}}",
            "type": "object"
          }
        },
        "type": "object"
      },
      "StaffSpaceInput": {
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "minGuests": {
            "type": "integer",
            "nullable": true
          },
          "maxGuests": {
            "type": "integer",
            "nullable": true
          },
          "partIds": {
            "description": "Makes it a combination of these plain spaces",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "sortOrder": {
            "type": "integer",
            "default": 0
          },
          "active": {
            "type": "boolean",
            "default": true
          },
          "hirePricing": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffSpaceHirePricing"
              }
            ],
            "nullable": true,
            "description": "Left out: unchanged; null clears it"
          },
          "translations": {
            "description": "Left out: unchanged; {} clears them",
            "type": "object"
          }
        },
        "type": "object"
      },
      "StaffDayPartHours": {
        "required": [
          "weekday",
          "start",
          "end"
        ],
        "properties": {
          "weekday": {
            "description": "ISO weekday, 1 = Monday",
            "type": "integer",
            "maximum": 7,
            "minimum": 1
          },
          "start": {
            "type": "string",
            "example": "09:00"
          },
          "end": {
            "type": "string",
            "example": "13:00"
          }
        },
        "type": "object"
      },
      "StaffDayPart": {
        "description": "A named part of the day at a location with its hours per weekday (#792)",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "locationId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "example": "Morning"
          },
          "hours": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffDayPartHours"
            }
          },
          "sortOrder": {
            "type": "integer"
          },
          "active": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "translations": {
            "description": "{\"fr\": {\"name\": \"…\"}}",
            "type": "object"
          }
        },
        "type": "object"
      },
      "StaffDayPartInput": {
        "required": [
          "name",
          "hours"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "hours": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffDayPartHours"
            }
          },
          "sortOrder": {
            "type": "integer",
            "default": 0
          },
          "active": {
            "type": "boolean",
            "default": true
          },
          "translations": {
            "description": "Left out: unchanged; {} clears them",
            "type": "object"
          }
        },
        "type": "object"
      },
      "StaffSpaceOccupation": {
        "description": "A space taken by a venue hire or a staff block (#792); sessions occupy through their own times",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "locationId": {
            "type": "string",
            "format": "uuid"
          },
          "spaceId": {
            "type": "string",
            "format": "uuid"
          },
          "startsAt": {
            "type": "string",
            "format": "date-time"
          },
          "endsAt": {
            "type": "string",
            "format": "date-time"
          },
          "source": {
            "type": "string",
            "enum": [
              "session",
              "hire",
              "block"
            ]
          },
          "reference": {
            "description": "The hire id for a hire",
            "type": "string",
            "nullable": true
          },
          "note": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "tentative",
              "confirmed",
              "released"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "confirmedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "releasedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffSpaceBlock": {
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/StaffSpaceOccupation"
          },
          {
            "properties": {
              "closedSessionIds": {
                "description": "Sessions sharing the space that the block closes",
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "type": "object"
          }
        ]
      },
      "StaffSpaceBlockInput": {
        "required": [
          "startsAt",
          "endsAt"
        ],
        "properties": {
          "spaceId": {
            "description": "Required when creating; a space of this location",
            "type": "string",
            "format": "uuid"
          },
          "startsAt": {
            "type": "string",
            "format": "date-time"
          },
          "endsAt": {
            "type": "string",
            "format": "date-time"
          },
          "note": {
            "type": "string",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffSpot": {
        "required": [
          "label",
          "x",
          "y"
        ],
        "properties": {
          "id": {
            "description": "Kept when given; new spots get one",
            "type": "string"
          },
          "label": {
            "type": "string",
            "example": "A1"
          },
          "type": {
            "description": "Free text, e.g. \"reformer\"",
            "type": "string",
            "nullable": true
          },
          "x": {
            "description": "Column, from 0",
            "type": "integer"
          },
          "y": {
            "description": "Row, from 0",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "StaffSpotLayout": {
        "description": "A room with named spots on a grid (#496)",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "locationId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "columns": {
            "type": "integer"
          },
          "rows": {
            "type": "integer"
          },
          "spots": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffSpot"
            }
          },
          "spotCount": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffSpotLayoutInput": {
        "required": [
          "name",
          "columns",
          "rows",
          "spots"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "columns": {
            "type": "integer"
          },
          "rows": {
            "type": "integer"
          },
          "spots": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffSpot"
            }
          }
        },
        "type": "object"
      },
      "StaffEquipment": {
        "description": "Equipment that can be assigned to sessions",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "totalQuantity": {
            "type": "integer"
          },
          "locationId": {
            "description": "Kept at one location, or null",
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffEquipmentInput": {
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "totalQuantity": {
            "type": "integer",
            "default": 1
          },
          "locationId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          }
        },
        "type": "object"
      },
      "EmailTestResult": {
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "message": {
            "type": "string",
            "example": "Test email sent successfully"
          }
        },
        "type": "object"
      },
      "EmailTemplateVariable": {
        "description": "A {{ placeholder }} the email may use",
        "properties": {
          "name": {
            "type": "string",
            "example": "customer_first_name"
          },
          "block": {
            "description": "A block (several lines, e.g. activity_info) that only fits in the body, never in the subject",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "EmailTemplateList": {
        "properties": {
          "languages": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "en",
                "nl",
                "fr",
                "de"
              ]
            }
          },
          "defaultLanguage": {
            "type": "string",
            "example": "nl"
          },
          "templates": {
            "type": "array",
            "items": {
              "properties": {
                "key": {
                  "type": "string",
                  "example": "booking_confirmed"
                },
                "variables": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/EmailTemplateVariable"
                  }
                },
                "languages": {
                  "description": "Per language: {customised, updatedAt}",
                  "type": "object",
                  "additionalProperties": {
                    "properties": {
                      "customised": {
                        "type": "boolean"
                      },
                      "updatedAt": {
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                      }
                    },
                    "type": "object"
                  }
                }
              },
              "type": "object"
            }
          }
        },
        "type": "object"
      },
      "EmailTemplate": {
        "description": "One email in one language: the wording in use and the default",
        "properties": {
          "key": {
            "type": "string",
            "example": "booking_confirmed"
          },
          "language": {
            "type": "string",
            "enum": [
              "en",
              "nl",
              "fr",
              "de"
            ]
          },
          "subject": {
            "type": "string"
          },
          "body": {
            "description": "Text with {{ placeholders }}",
            "type": "string"
          },
          "customised": {
            "description": "False: the default wording is in use",
            "type": "boolean"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "default": {
            "properties": {
              "subject": {
                "type": "string"
              },
              "body": {
                "type": "string"
              }
            },
            "type": "object"
          },
          "variables": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EmailTemplateVariable"
            }
          }
        },
        "type": "object"
      },
      "EmailTemplateDraft": {
        "description": "A subject and body to try out; left out or empty: the wording in use",
        "properties": {
          "subject": {
            "type": "string"
          },
          "body": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "EmailTemplateProblems": {
        "properties": {
          "error": {
            "type": "string"
          },
          "problems": {
            "description": "Per field (subject, body): what is wrong, e.g. an unknown placeholder",
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        },
        "type": "object"
      },
      "SentSms": {
        "properties": {
          "id": {
            "type": "string"
          },
          "customerId": {
            "description": "Null for a test message",
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "to": {
            "description": "E.164",
            "type": "string",
            "example": "+32470123456"
          },
          "body": {
            "type": "string"
          },
          "message": {
            "description": "Which message: session_reminder, session_cancelled, waitlist_spot, appointment_moved or test",
            "type": "string"
          },
          "language": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "sent",
              "delivered",
              "undelivered",
              "failed",
              "rate_limited"
            ]
          },
          "provider": {
            "description": "twilio, or log outside production",
            "type": "string",
            "nullable": true
          },
          "errorCode": {
            "type": "string",
            "nullable": true
          },
          "errorMessage": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "SmsSettingsInput": {
        "description": "The whole text message configuration; fields left out take their default",
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "accountSid": {
            "description": "Twilio Account SID: AC + 32 hex characters",
            "type": "string",
            "nullable": true
          },
          "authToken": {
            "description": "Left out or empty: the stored token is kept. Stored encrypted, never returned",
            "type": "string",
            "nullable": true
          },
          "sender": {
            "description": "A number in international format (+32…) or a sender ID of up to 11 letters and digits",
            "type": "string",
            "nullable": true
          },
          "messages": {
            "description": "Which messages also go by SMS",
            "properties": {
              "session_reminder": {
                "type": "boolean"
              },
              "session_cancelled": {
                "type": "boolean"
              },
              "waitlist_spot": {
                "type": "boolean"
              },
              "appointment_moved": {
                "type": "boolean"
              }
            },
            "type": "object"
          },
          "dailyLimit": {
            "type": "integer",
            "default": 500,
            "maximum": 10000,
            "minimum": 1
          },
          "defaultCountry": {
            "description": "Two-letter country of numbers typed without a country code",
            "type": "string",
            "default": "BE"
          }
        },
        "type": "object"
      },
      "SmsSettings": {
        "properties": {
          "settings": {
            "properties": {
              "enabled": {
                "type": "boolean"
              },
              "accountSid": {
                "type": "string",
                "nullable": true
              },
              "authTokenSet": {
                "description": "Whether a token is stored; the token itself is never returned",
                "type": "boolean"
              },
              "sender": {
                "type": "string",
                "nullable": true
              },
              "messages": {
                "type": "object",
                "additionalProperties": {
                  "type": "boolean"
                }
              },
              "dailyLimit": {
                "type": "integer"
              },
              "defaultCountry": {
                "type": "string"
              }
            },
            "type": "object"
          },
          "connected": {
            "type": "boolean"
          },
          "localLogOnly": {
            "description": "Outside production nothing reaches Twilio: messages are only logged",
            "type": "boolean"
          },
          "webhooks": {
            "description": "URLs to set on the Twilio number",
            "properties": {
              "inbound": {
                "type": "string",
                "format": "uri"
              },
              "status": {
                "type": "string",
                "format": "uri"
              }
            },
            "type": "object"
          },
          "templates": {
            "type": "array",
            "items": {
              "properties": {
                "message": {
                  "type": "string",
                  "enum": [
                    "session_reminder",
                    "session_cancelled",
                    "waitlist_spot",
                    "appointment_moved"
                  ]
                },
                "variables": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "languages": {
                  "description": "Per language (en, nl, fr, de)",
                  "type": "object",
                  "additionalProperties": {
                    "properties": {
                      "text": {
                        "type": "string"
                      },
                      "default": {
                        "type": "string"
                      },
                      "customised": {
                        "type": "boolean"
                      }
                    },
                    "type": "object"
                  }
                }
              },
              "type": "object"
            }
          },
          "samples": {
            "description": "Per language: example values of the placeholders",
            "type": "object",
            "additionalProperties": {
              "type": "object"
            }
          },
          "maxTemplateLength": {
            "type": "integer",
            "example": 480
          }
        },
        "type": "object"
      },
      "CustomerSegment": {
        "description": "A group of customers: plan_expiring (holds a pass/subscription ending within `days`), inactive (no booked session in the last `days` days nor one ahead), activity_booked (booked a session of `activityId` between `from` and `to`), waitlist (on a waiting list of `activityId`), customers (`customerIds`, 1–1000), tag (any of `tagIds`, 1–20)",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "plan_expiring",
              "inactive",
              "activity_booked",
              "waitlist",
              "customers",
              "tag"
            ]
          },
          "days": {
            "type": "integer",
            "maximum": 3650,
            "minimum": 1
          },
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "from": {
            "type": "string",
            "format": "date"
          },
          "to": {
            "description": "Inclusive",
            "type": "string",
            "format": "date"
          },
          "customerIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "tagIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "type": "object"
      },
      "SegmentMessage": {
        "properties": {
          "id": {
            "type": "string"
          },
          "segment": {
            "$ref": "#/components/schemas/CustomerSegment"
          },
          "kind": {
            "description": "marketing only reaches customers who consented to marketing email",
            "type": "string",
            "enum": [
              "service",
              "marketing"
            ]
          },
          "subject": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "sending",
              "sent"
            ]
          },
          "recipientCount": {
            "type": "integer"
          },
          "sentCount": {
            "type": "integer"
          },
          "failedCount": {
            "type": "integer"
          },
          "sentBy": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "sentAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "Money": {
        "description": "Monetary value representation",
        "required": [
          "amount",
          "currency",
          "formatted"
        ],
        "properties": {
          "amount": {
            "description": "Amount in smallest currency unit (e.g., cents)",
            "type": "integer",
            "example": 2500
          },
          "currency": {
            "description": "ISO 4217 currency code",
            "type": "string",
            "example": "EUR"
          },
          "formatted": {
            "description": "Human-readable formatted amount",
            "type": "string",
            "example": "€25.00"
          }
        },
        "type": "object"
      },
      "OrgSettingsBookingRules": {
        "description": "The organisation's attendance and booking rules. On PUT every section is optional; a section left out takes its defaults",
        "properties": {
          "attendance": {
            "properties": {
              "noShowAutomatic": {
                "description": "Mark not checked-in attendees as no-show automatically",
                "type": "boolean",
                "default": true
              },
              "noShowAfterHours": {
                "description": "Hours after the session ends",
                "type": "integer",
                "maximum": 336,
                "minimum": 0
              }
            },
            "type": "object"
          },
          "window": {
            "description": "Default booking window",
            "properties": {
              "opensDaysBefore": {
                "description": "Null: always open",
                "type": "integer",
                "nullable": true
              },
              "closesHoursBefore": {
                "description": "Null: until the start",
                "type": "integer",
                "nullable": true
              }
            },
            "type": "object"
          },
          "absences": {
            "description": "Customers reporting an absence",
            "properties": {
              "enabled": {
                "type": "boolean",
                "default": true
              },
              "cutoffHours": {
                "type": "integer",
                "maximum": 336,
                "minimum": 0
              },
              "makeUpCredits": {
                "type": "boolean",
                "default": false
              },
              "makeUpCreditWeeks": {
                "type": "integer",
                "maximum": 52,
                "minimum": 1
              }
            },
            "type": "object"
          },
          "lateSignUp": {
            "description": "Joining a series that has started",
            "properties": {
              "allowed": {
                "type": "boolean"
              },
              "untilSession": {
                "description": "Allowed until this session number has started; null: until the last",
                "type": "integer",
                "nullable": true,
                "maximum": 1000,
                "minimum": 1
              },
              "pricing": {
                "type": "string",
                "enum": [
                  "pro_rated",
                  "full"
                ]
              }
            },
            "type": "object"
          },
          "waitlist": {
            "properties": {
              "holdHours": {
                "description": "How long a freed seat is kept for the next in line",
                "type": "integer",
                "default": 24,
                "maximum": 168,
                "minimum": 1
              }
            },
            "type": "object"
          },
          "checkout": {
            "properties": {
              "phoneRequired": {
                "type": "boolean",
                "default": true
              }
            },
            "type": "object"
          },
          "fees": {
            "description": "Late-cancellation and no-show fees (#406)",
            "properties": {
              "dueDays": {
                "type": "integer",
                "default": 14
              },
              "blockBookingWhenOverdue": {
                "type": "boolean",
                "default": false
              }
            },
            "type": "object"
          },
          "display": {
            "properties": {
              "showSpotsLeft": {
                "type": "boolean",
                "default": true
              },
              "spotsLeftThreshold": {
                "description": "Show the count only at this many spots left or fewer; null: always",
                "type": "integer",
                "nullable": true
              }
            },
            "type": "object"
          },
          "followUps": {
            "description": "Abandoned checkout and spots-left emails (#747), both off by default",
            "properties": {
              "abandonedReminder": {
                "type": "boolean"
              },
              "abandonedReminderHours": {
                "type": "integer"
              },
              "spotsLeft": {
                "type": "boolean"
              },
              "spotsLeftHoursBefore": {
                "type": "integer"
              },
              "spotsLeftMax": {
                "type": "integer"
              }
            },
            "type": "object"
          },
          "reminders": {
            "description": "Session reminders (#740); left out on PUT: unchanged",
            "properties": {
              "enabled": {
                "type": "boolean"
              },
              "hoursBefore": {
                "type": "integer"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "OrgSettingsOnboardingStep": {
        "properties": {
          "key": {
            "type": "string",
            "enum": [
              "location",
              "activity",
              "series",
              "activate",
              "payments",
              "terms",
              "share",
              "cancellation_policy",
              "reminders",
              "email_sender",
              "closures",
              "team",
              "customers"
            ]
          },
          "group": {
            "type": "string",
            "enum": [
              "launch",
              "first_weeks"
            ]
          },
          "done": {
            "type": "boolean"
          },
          "skipped": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "OrgSettingsTermsVersion": {
        "description": "A version of the organisation's terms and conditions; customers accept the active one when booking",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "organisationId": {
            "type": "string",
            "format": "uuid"
          },
          "versionNumber": {
            "type": "integer"
          },
          "title": {
            "type": "string"
          },
          "content": {
            "type": "string"
          },
          "summary": {
            "type": "string",
            "nullable": true
          },
          "effectiveFrom": {
            "type": "string",
            "format": "date-time"
          },
          "effectiveUntil": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "isActive": {
            "type": "boolean"
          },
          "isDraft": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "publishedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "OrgSettingsRetention": {
        "description": "Retention periods (#400); null: kept until removed by hand",
        "properties": {
          "inactiveCustomerYears": {
            "description": "Anonymise customers inactive this many years",
            "type": "integer",
            "nullable": true,
            "maximum": 120,
            "minimum": 1
          },
          "unverifiedCustomerMonths": {
            "description": "Remove never-verified accounts without bookings after this many months",
            "type": "integer",
            "nullable": true,
            "maximum": 120,
            "minimum": 1
          },
          "bookingAnswersMonths": {
            "description": "Clear answers to booking questions after this many months",
            "type": "integer",
            "nullable": true,
            "maximum": 120,
            "minimum": 1
          }
        },
        "type": "object"
      },
      "OrgSettingsPrivacyLogEntry": {
        "properties": {
          "id": {
            "type": "string"
          },
          "action": {
            "description": "customer.anonymised, customer.deletion_requested, customer.data_exported, export.*, retention.*",
            "type": "string",
            "example": "customer.data_exported"
          },
          "subjectType": {
            "type": "string"
          },
          "subjectId": {
            "type": "string",
            "nullable": true
          },
          "actor": {
            "description": "Null for automatic actions",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "description": "Null for a removed staff account",
                "type": "string",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "details": {
            "type": "object"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "CustomFieldDefinition": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "entityType": {
            "type": "string",
            "enum": [
              "customer",
              "participant",
              "booking"
            ]
          },
          "fieldName": {
            "description": "Machine name, unique per entity type",
            "type": "string"
          },
          "fieldType": {
            "type": "string",
            "enum": [
              "boolean",
              "string",
              "integer",
              "date",
              "choice",
              "consent",
              "phone"
            ]
          },
          "label": {
            "type": "string"
          },
          "isRequired": {
            "type": "boolean"
          },
          "visibleToCoaches": {
            "description": "Shown on the coach's roster (#750)",
            "type": "boolean"
          },
          "sortOrder": {
            "type": "integer"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "options": {
            "description": "The choices of a choice field",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "perParticipant": {
            "description": "Booking fields: asked per participant or once per booking",
            "type": "boolean"
          },
          "activityIds": {
            "description": "Booking fields: only for these activities (empty: all)",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "seriesIds": {
            "description": "Booking fields: only for these series (empty: all)",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "version": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "CustomFieldDefinitionInput": {
        "description": "On create entityType, fieldName, fieldType and label are required; on update label is required and entityType, fieldName and fieldType can't change (other fields left out stay as they are)",
        "required": [
          "label"
        ],
        "properties": {
          "entityType": {
            "type": "string",
            "enum": [
              "customer",
              "participant",
              "booking"
            ]
          },
          "fieldName": {
            "type": "string"
          },
          "fieldType": {
            "type": "string",
            "enum": [
              "boolean",
              "string",
              "integer",
              "date",
              "choice",
              "consent",
              "phone"
            ]
          },
          "label": {
            "type": "string"
          },
          "isRequired": {
            "type": "boolean",
            "default": false
          },
          "sortOrder": {
            "type": "integer",
            "default": 0
          },
          "visibleToCoaches": {
            "type": "boolean",
            "default": false
          },
          "description": {
            "description": "On update an empty string clears it",
            "type": "string",
            "nullable": true
          },
          "options": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "perParticipant": {
            "type": "boolean",
            "default": true
          },
          "activityIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "seriesIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "type": "object"
      },
      "CustomFieldTemplate": {
        "description": "A preset bundle of custom fields (#750)",
        "properties": {
          "key": {
            "description": "emergency_contact, pickup, medical, photo_consent, swimming, club_membership",
            "type": "string",
            "example": "emergency_contact"
          },
          "icon": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "fields": {
            "type": "array",
            "items": {
              "properties": {
                "entityType": {
                  "type": "string",
                  "enum": [
                    "customer",
                    "participant",
                    "booking"
                  ]
                },
                "fieldName": {
                  "type": "string"
                },
                "fieldType": {
                  "type": "string",
                  "enum": [
                    "boolean",
                    "string",
                    "integer",
                    "date",
                    "choice",
                    "consent",
                    "phone"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "description": {
                  "type": "string",
                  "nullable": true
                },
                "visibleToCoaches": {
                  "type": "boolean"
                },
                "exists": {
                  "description": "The organisation has a field with this name already",
                  "type": "boolean"
                }
              },
              "type": "object"
            }
          },
          "added": {
            "description": "Every field of it exists",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "StaffCustomerTag": {
        "description": "A manual customer tag (#758) with how many customers carry it",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "maxLength": 40
          },
          "colour": {
            "type": "string",
            "enum": [
              "grey",
              "red",
              "orange",
              "yellow",
              "green",
              "teal",
              "blue",
              "purple",
              "pink"
            ]
          },
          "customerCount": {
            "type": "integer"
          }
        },
        "type": "object"
      },
      "ReviewAdminReview": {
        "description": "A customer review as staff see it (#355)",
        "properties": {
          "id": {
            "type": "string"
          },
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "activityName": {
            "type": "string",
            "nullable": true
          },
          "coach": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "rating": {
            "type": "integer",
            "maximum": 5,
            "minimum": 1
          },
          "comment": {
            "type": "string",
            "nullable": true
          },
          "reply": {
            "description": "The organisation's public answer",
            "type": "string",
            "nullable": true
          },
          "repliedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "published",
              "hidden"
            ]
          },
          "customer": {
            "description": "id and email only for staff with customer:read:all",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              },
              "name": {
                "type": "string",
                "nullable": true
              },
              "email": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object"
          },
          "repliedBy": {
            "description": "Name of the staff member who replied",
            "type": "string",
            "nullable": true
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "ReviewAdminSettings": {
        "description": "Review settings; on PUT fields left out stay as they are",
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "requestEmails": {
            "description": "Email attendees asking for a review",
            "type": "boolean"
          },
          "termWeeks": {
            "description": "One review per customer and activity per this many weeks",
            "type": "integer",
            "maximum": 52,
            "minimum": 1
          },
          "autoPublish": {
            "type": "boolean"
          },
          "showPublicly": {
            "type": "boolean"
          },
          "googleReviewUrl": {
            "description": "https link to the Google review page (#753); null or blank clears it",
            "type": "string",
            "format": "uri",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffParticipant": {
        "description": "A participant as staff see it. Coaches (participant:read:assigned) only get id, names, dateOfBirth, internalNotes and metadata.customFields limited to the fields visible to coaches (#729); customerIds needs customer:read.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "customerIds": {
            "description": "The customer accounts managing this participant; only with customer:read",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "fullName": {
            "type": "string"
          },
          "dateOfBirth": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "nationalRegisterNumber": {
            "type": "string",
            "nullable": true
          },
          "tShirtSize": {
            "type": "string",
            "nullable": true
          },
          "remarks": {
            "type": "string",
            "nullable": true
          },
          "internalNotes": {
            "description": "Staff only, never shown to customers",
            "type": "string",
            "nullable": true
          },
          "metadata": {
            "description": "Custom field values under `customFields` (field name => value)",
            "properties": {
              "customFields": {
                "type": "object",
                "additionalProperties": true
              }
            },
            "type": "object"
          },
          "possibleDuplicate": {
            "description": "Flagged as a possible duplicate (#426)",
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffParticipantDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/StaffParticipant"
          },
          {
            "properties": {
              "customFields": {
                "description": "The custom field values with their labels, in the definitions' order (#415)",
                "type": "array",
                "items": {
                  "properties": {
                    "fieldName": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "fieldType": {
                      "type": "string"
                    },
                    "value": {}
                  },
                  "type": "object"
                }
              }
            },
            "type": "object"
          }
        ]
      },
      "StaffParticipantInput": {
        "description": "An update replaces every field: fields left out are cleared",
        "required": [
          "firstName",
          "lastName"
        ],
        "properties": {
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "customerIds": {
            "description": "Create only: the customers managing this participant",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "dateOfBirth": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "nationalRegisterNumber": {
            "type": "string",
            "nullable": true
          },
          "tShirtSize": {
            "type": "string",
            "nullable": true
          },
          "remarks": {
            "type": "string",
            "nullable": true
          },
          "metadata": {
            "description": "Custom field values under `customFields`",
            "properties": {
              "customFields": {
                "type": "object",
                "additionalProperties": true
              }
            },
            "type": "object"
          },
          "internalNotes": {
            "type": "string",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffParticipantMergeView": {
        "description": "A participant as the merge tool shows it (#426)",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "customerIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "fullName": {
            "type": "string"
          },
          "dateOfBirth": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "nationalRegisterNumber": {
            "type": "string",
            "nullable": true
          },
          "tShirtSize": {
            "type": "string",
            "nullable": true
          },
          "remarks": {
            "type": "string",
            "nullable": true
          },
          "internalNotes": {
            "type": "string",
            "nullable": true
          },
          "customFields": {
            "description": "Field name => value",
            "type": "object",
            "additionalProperties": true
          },
          "possibleDuplicate": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "StaffParticipantReferences": {
        "description": "What points at a participant and would move on a merge",
        "properties": {
          "bookings": {
            "type": "integer"
          },
          "checkIns": {
            "type": "integer"
          },
          "waitlistEntries": {
            "type": "integer"
          }
        },
        "type": "object"
      },
      "StaffParticipantAttendance": {
        "description": "Attendance of a participant in sessions that already started (#343), newest first, at most 50 listed",
        "properties": {
          "participantId": {
            "type": "string",
            "format": "uuid"
          },
          "totals": {
            "properties": {
              "present": {
                "type": "integer"
              },
              "absent": {
                "type": "integer"
              },
              "excused": {
                "type": "integer"
              },
              "unmarked": {
                "type": "integer"
              },
              "rate": {
                "description": "present / (present + absent), 3 decimals; null when none counted",
                "type": "number",
                "nullable": true
              }
            },
            "type": "object"
          },
          "sessions": {
            "type": "array",
            "items": {
              "properties": {
                "sessionId": {
                  "type": "string",
                  "format": "uuid"
                },
                "seriesId": {
                  "type": "string",
                  "format": "uuid",
                  "nullable": true
                },
                "activityName": {
                  "type": "string",
                  "nullable": true
                },
                "startTime": {
                  "type": "string",
                  "format": "date-time"
                },
                "attendance": {
                  "type": "string",
                  "enum": [
                    "present",
                    "absent",
                    "excused",
                    "unmarked"
                  ]
                }
              },
              "type": "object"
            }
          }
        },
        "type": "object"
      },
      "StaffParticipantIncident": {
        "description": "An incident report (#751)",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "session": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "startTime": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "activityName": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object"
          },
          "participant": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "fullName": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object"
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time"
          },
          "description": {
            "type": "string"
          },
          "injury": {
            "type": "string",
            "nullable": true
          },
          "actionsTaken": {
            "type": "string",
            "nullable": true
          },
          "peopleInformed": {
            "type": "string",
            "nullable": true
          },
          "parentsInformed": {
            "type": "boolean"
          },
          "parentsInformedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "parentAcknowledged": {
            "type": "boolean"
          },
          "parentAcknowledgedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "parentAcknowledgedBy": {
            "type": "string",
            "nullable": true
          },
          "reportedBy": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "updatedBy": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "PaymentGateway": {
        "description": "A payment gateway. Credentials never leave the server: configuration says per credential whether it is set.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "organisationId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "mollie",
              "stripe",
              "paypal",
              "worldline",
              "payconiq",
              "adyen"
            ]
          },
          "typeName": {
            "type": "string",
            "example": "Mollie"
          },
          "configuration": {
            "description": "Settings as stored, secrets masked as true/false (set or not)",
            "type": "object"
          },
          "webhookUrl": {
            "description": "The address to enter at the provider, for gateways whose notifications are configured there",
            "type": "string",
            "nullable": true
          },
          "canTestConnection": {
            "type": "boolean"
          },
          "mollie": {
            "description": "Mollie only (#910)",
            "properties": {
              "connection": {
                "type": "string",
                "enum": [
                  "oauth",
                  "api_key"
                ]
              },
              "connected": {
                "type": "boolean"
              },
              "needsReconnect": {
                "type": "boolean"
              },
              "needsProfile": {
                "description": "Connected but no website profile picked yet",
                "type": "boolean"
              },
              "organizationName": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "isActive": {
            "type": "boolean"
          },
          "supportedCurrencies": {
            "description": "What it can charge in; Plek never converts (#350)",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "PaymentGatewayInput": {
        "required": [
          "name",
          "type",
          "configuration"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "type": {
            "description": "Not changeable on update",
            "type": "string",
            "enum": [
              "mollie",
              "stripe",
              "paypal",
              "worldline",
              "payconiq",
              "adyen"
            ]
          },
          "configuration": {
            "description": "Provider settings, e.g. Mollie {api_key, testmode}, Worldline {merchant_id, api_key, api_secret, webhook_key_id, webhook_secret, is_production}; a secret left out keeps the stored one.",
            "type": "object"
          }
        },
        "type": "object"
      },
      "OfflinePaymentSettings": {
        "description": "Paying later: on site or by bank transfer (#397)",
        "properties": {
          "payOnSiteEnabled": {
            "type": "boolean"
          },
          "bankTransferEnabled": {
            "description": "Needs an IBAN",
            "type": "boolean"
          },
          "iban": {
            "type": "string",
            "nullable": true
          },
          "bic": {
            "type": "string",
            "nullable": true
          },
          "accountHolder": {
            "type": "string",
            "nullable": true
          },
          "paymentDeadlineDays": {
            "type": "integer",
            "default": 7,
            "maximum": 365,
            "minimum": 0
          },
          "reminderDaysBeforeDeadline": {
            "type": "integer",
            "default": 2,
            "maximum": 60,
            "minimum": 0
          },
          "cancelUnpaidAfterDeadline": {
            "type": "boolean"
          },
          "overdueReminderDays": {
            "description": "Days after the deadline to remind again",
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 1
            },
            "maxItems": 3
          }
        },
        "type": "object"
      },
      "StaffCustomerCharge": {
        "description": "A fee or hire charge as staff see it",
        "allOf": [
          {
            "$ref": "#/components/schemas/CustomerCharge"
          },
          {
            "properties": {
              "customerId": {
                "type": "string",
                "format": "uuid"
              },
              "participantId": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              },
              "paidMethod": {
                "description": "cash, card, bank_transfer or other when staff recorded it, else how it was paid online",
                "type": "string",
                "nullable": true
              },
              "waiveReason": {
                "type": "string",
                "nullable": true
              },
              "waivedBy": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              }
            },
            "type": "object"
          }
        ]
      },
      "StaffServiceRule": {
        "description": "An activity (at any location) and how often it may be booked",
        "properties": {
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "rule": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "unlimited",
                  "limited",
                  "credits"
                ]
              },
              "count": {
                "description": "limited: uses per period",
                "type": "integer",
                "nullable": true
              },
              "per": {
                "description": "limited: the period (calendar weeks Mon-Sun)",
                "type": "string",
                "nullable": true,
                "enum": [
                  "week",
                  "month",
                  "validity"
                ]
              },
              "cost": {
                "description": "credits: credits per session (default 1)",
                "type": "integer",
                "nullable": true
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "StaffPlan": {
        "description": "A subscription or pass on sale (#378)",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "subscription",
              "pass"
            ]
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "price": {
            "$ref": "#/components/schemas/StaffAmount"
          },
          "taxCategoryId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "services": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffServiceRule"
            }
          },
          "credits": {
            "description": "Credits of a pass",
            "type": "integer",
            "nullable": true
          },
          "duration": {
            "$ref": "#/components/schemas/StaffPlanDuration"
          },
          "fixedStartDate": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "availableFrom": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "availableUntil": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "maxSold": {
            "type": "integer",
            "nullable": true
          },
          "active": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "productCode": {
            "type": "string",
            "nullable": true
          },
          "translations": {
            "description": "{\"fr\": {\"name\": \"…\", \"description\": \"…\"}}",
            "type": "object"
          },
          "sold": {
            "description": "Holdings ever granted from it",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "StaffPlanDuration": {
        "description": "Relative (count + unit, ending the day before the same date) or a fixed end date",
        "properties": {
          "count": {
            "type": "integer",
            "example": 3,
            "nullable": true
          },
          "unit": {
            "type": "string",
            "nullable": true,
            "enum": [
              "days",
              "weeks",
              "months",
              "years"
            ]
          },
          "endsAt": {
            "type": "string",
            "format": "date",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffPlanInput": {
        "required": [
          "kind",
          "name",
          "price",
          "services"
        ],
        "properties": {
          "kind": {
            "description": "Fixed once created",
            "type": "string",
            "enum": [
              "subscription",
              "pass"
            ]
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "price": {
            "required": [
              "amount"
            ],
            "properties": {
              "amount": {
                "description": "Cents",
                "type": "integer"
              },
              "currency": {
                "description": "Default: the organisation's currency",
                "type": "string"
              }
            },
            "type": "object"
          },
          "taxCategoryId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "services": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffServiceRule"
            }
          },
          "duration": {
            "description": "{count, unit} or {endsAt}; default 12 months for a pass",
            "properties": {
              "count": {
                "type": "integer"
              },
              "unit": {
                "type": "string",
                "enum": [
                  "days",
                  "weeks",
                  "months",
                  "years"
                ]
              },
              "endsAt": {
                "type": "string",
                "format": "date"
              }
            },
            "type": "object",
            "nullable": true
          },
          "credits": {
            "description": "Pass: credits it holds",
            "type": "integer",
            "nullable": true
          },
          "fixedStartDate": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "availableFrom": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "availableUntil": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "maxSold": {
            "type": "integer",
            "nullable": true
          },
          "active": {
            "type": "boolean",
            "default": true
          },
          "productCode": {
            "description": "Left out: unchanged",
            "type": "string",
            "nullable": true
          },
          "translations": {
            "description": "Left out: unchanged; {} clears them",
            "type": "object"
          }
        },
        "type": "object"
      },
      "StaffEntitlement": {
        "description": "Something a customer holds: a subscription, pass, make-up credit, series perk or manual credit (#374)",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "sourceType": {
            "type": "string",
            "enum": [
              "absence",
              "subscription",
              "pass",
              "series_perk",
              "manual"
            ]
          },
          "sourceId": {
            "description": "The plan, absence or series booking behind it",
            "type": "string",
            "nullable": true
          },
          "customerId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "participantId": {
            "description": "Set for a subscription, bound to one participant",
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "participantName": {
            "type": "string",
            "nullable": true
          },
          "services": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffServiceRule"
            }
          },
          "credits": {
            "type": "integer",
            "nullable": true
          },
          "balance": {
            "description": "Credits left, computed from the ledger; null without credits",
            "type": "integer",
            "nullable": true
          },
          "used": {
            "description": "Active bookings made with it",
            "type": "integer"
          },
          "validFrom": {
            "type": "string",
            "format": "date-time"
          },
          "validUntil": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "cancelled"
            ]
          },
          "expired": {
            "type": "boolean"
          },
          "usable": {
            "type": "boolean"
          },
          "metadata": {
            "type": "object"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "cancelledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "cancellationReason": {
            "type": "string",
            "nullable": true
          },
          "purchase": {
            "description": "The online purchase behind it (#536); only on single holdings and a customer's list",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "paid": {
                "$ref": "#/components/schemas/StaffAmount"
              },
              "amountDue": {
                "$ref": "#/components/schemas/StaffAmount"
              },
              "refunded": {
                "$ref": "#/components/schemas/StaffAmount"
              },
              "refundable": {
                "$ref": "#/components/schemas/StaffAmount"
              },
              "paidAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "receiptAvailable": {
                "type": "boolean"
              }
            },
            "type": "object",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffEntitlementUsage": {
        "description": "A line of the usage ledger",
        "properties": {
          "id": {
            "type": "string"
          },
          "entitlementId": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "booking",
              "adjustment"
            ]
          },
          "units": {
            "description": "Credits this line takes (a booking) or changes (an adjustment)",
            "type": "integer"
          },
          "bookingId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sessionId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "activityId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sessionStartsAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "participantId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "reason": {
            "type": "string",
            "nullable": true
          },
          "createdBy": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "reversedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "reversalReason": {
            "type": "string",
            "nullable": true
          },
          "activityName": {
            "type": "string",
            "nullable": true
          },
          "participantName": {
            "type": "string",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffPlanPurchase": {
        "description": "A plan bought online or to pay later (#377, #607)",
        "allOf": [
          {
            "$ref": "#/components/schemas/CustomerPlanPurchase"
          },
          {
            "properties": {
              "paymentReference": {
                "description": "Structured reference for a bank transfer (only in awaitingPayment)",
                "type": "string",
                "nullable": true
              }
            },
            "type": "object"
          }
        ]
      },
      "PosSaleItemInput": {
        "description": "One item of a sale",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "session",
              "plan",
              "gift_card"
            ]
          },
          "sessionId": {
            "description": "session",
            "type": "string",
            "format": "uuid"
          },
          "participantId": {
            "description": "session of a participants activity; a subscription plan",
            "type": "string",
            "format": "uuid"
          },
          "tickets": {
            "description": "session of a headcount activity (#898): the party instead of a participant",
            "type": "array",
            "items": {
              "properties": {
                "ticketTypeId": {
                  "type": "string",
                  "format": "uuid"
                },
                "quantity": {
                  "type": "integer"
                }
              },
              "type": "object"
            }
          },
          "extras": {
            "description": "With tickets",
            "type": "array",
            "items": {
              "properties": {
                "extraId": {
                  "type": "string",
                  "format": "uuid"
                },
                "quantity": {
                  "type": "integer"
                }
              },
              "type": "object"
            }
          },
          "planId": {
            "description": "plan",
            "type": "string",
            "format": "uuid"
          },
          "startDate": {
            "description": "plan",
            "type": "string",
            "format": "date"
          },
          "amount": {
            "description": "gift_card: value in cents",
            "type": "integer",
            "minimum": 1
          },
          "recipientName": {
            "description": "gift_card",
            "type": "string"
          },
          "recipientEmail": {
            "description": "gift_card",
            "type": "string"
          }
        },
        "type": "object"
      },
      "PosLine": {
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "session",
              "plan",
              "gift_card"
            ]
          },
          "description": {
            "type": "string"
          },
          "price": {
            "$ref": "#/components/schemas/StaffAmount"
          },
          "discount": {
            "$ref": "#/components/schemas/StaffAmount"
          },
          "total": {
            "$ref": "#/components/schemas/StaffAmount"
          }
        },
        "type": "object"
      },
      "PosSale": {
        "properties": {
          "id": {
            "type": "string"
          },
          "customerId": {
            "type": "string",
            "format": "uuid"
          },
          "paymentMethod": {
            "type": "string",
            "enum": [
              "cash",
              "cardterminal",
              "link"
            ]
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PosLine"
            }
          },
          "total": {
            "$ref": "#/components/schemas/StaffAmount"
          },
          "links": {
            "description": "paymentMethod link: the payment pages to pay online",
            "type": "array",
            "items": {
              "properties": {
                "kind": {
                  "type": "string",
                  "enum": [
                    "session",
                    "plan",
                    "gift_card"
                  ]
                },
                "url": {
                  "type": "string",
                  "nullable": true
                },
                "amount": {
                  "$ref": "#/components/schemas/StaffAmount"
                }
              },
              "type": "object"
            }
          },
          "bookingIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "planPurchaseIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "giftCardIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "giftCardCodes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "StaffAbandonedCheckout": {
        "description": "A checkout whose bookings all expired unpaid (#747)",
        "properties": {
          "checkoutId": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "session",
              "series"
            ]
          },
          "sessionId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "seriesId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "activityName": {
            "type": "string",
            "nullable": true
          },
          "seriesName": {
            "type": "string",
            "nullable": true
          },
          "locationName": {
            "type": "string",
            "nullable": true
          },
          "startsAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "startedAt": {
            "description": "When the customer started the checkout",
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "expiredAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "bookings": {
            "type": "integer"
          },
          "seats": {
            "type": "integer"
          },
          "party": {
            "type": "boolean"
          },
          "participantNames": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "amount": {
            "$ref": "#/components/schemas/StaffAmount"
          },
          "customerId": {
            "description": "Only with customer:read",
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "customerName": {
            "description": "Only with customer:read",
            "type": "string",
            "nullable": true
          },
          "customerEmail": {
            "description": "Only with customer:read",
            "type": "string",
            "nullable": true
          },
          "bookedSince": {
            "description": "The customer holds the item again",
            "type": "boolean"
          },
          "reminderSentAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "payLinkSentAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffWaitlistCounts": {
        "description": "People in line",
        "properties": {
          "waiting": {
            "type": "integer"
          },
          "offered": {
            "description": "A spot is kept for them",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "StaffWaitlistList": {
        "description": "The waiting list of one session or series still to come",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "session",
              "series"
            ]
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "activityId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "activityName": {
            "type": "string",
            "nullable": true
          },
          "seriesId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "seriesName": {
            "type": "string",
            "nullable": true
          },
          "locationId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "locationName": {
            "type": "string",
            "nullable": true
          },
          "startsAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "endsAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "waiting": {
            "type": "integer"
          },
          "offered": {
            "type": "integer"
          },
          "seatsWaiting": {
            "description": "Seats the people waiting would take: a party counts its seats (#796)",
            "type": "integer"
          },
          "waitingSince": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "entries": {
            "description": "In line order",
            "type": "array",
            "items": {
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "participantId": {
                  "type": "string",
                  "format": "uuid",
                  "nullable": true
                },
                "participantName": {
                  "type": "string",
                  "nullable": true
                },
                "customerId": {
                  "description": "Only with customer:read",
                  "type": "string",
                  "format": "uuid",
                  "nullable": true
                },
                "customerName": {
                  "description": "Only with customer:read",
                  "type": "string",
                  "nullable": true
                },
                "customerEmail": {
                  "description": "Only with customer:read",
                  "type": "string",
                  "nullable": true
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "waiting",
                    "notified"
                  ]
                },
                "origin": {
                  "type": "string",
                  "enum": [
                    "waitlist",
                    "reenrolment"
                  ]
                },
                "position": {
                  "type": "integer"
                },
                "createdAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                },
                "notifiedAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                },
                "expiresAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                },
                "seats": {
                  "type": "integer"
                },
                "partySummary": {
                  "description": "A party: its tickets, no names",
                  "type": "string",
                  "nullable": true
                }
              },
              "type": "object"
            }
          }
        },
        "type": "object"
      },
      "PublicActivity": {
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "example": "Morning Yoga"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "defaultPrice": {
            "$ref": "#/components/schemas/Money"
          },
          "images": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Image"
            }
          }
        },
        "type": "object"
      },
      "PublicAuthCustomer": {
        "description": "The signed-in customer",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "language": {
            "type": "string",
            "nullable": true
          },
          "emailVerified": {
            "description": "Signing in with a code, link or Google/Microsoft verifies the address",
            "type": "boolean"
          },
          "hasPassword": {
            "type": "boolean"
          },
          "marketingOptIn": {
            "type": "boolean"
          },
          "smsOptIn": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "PublicAuthTokens": {
        "properties": {
          "accessToken": {
            "description": "Send as `Authorization: Bearer ...` on /api/customer/{org_slug} routes",
            "type": "string"
          },
          "tokenType": {
            "type": "string",
            "example": "Bearer"
          },
          "expiresIn": {
            "description": "Seconds the access token stays valid",
            "type": "integer"
          },
          "refreshToken": {
            "description": "Single use: /auth/refresh answers a new pair",
            "type": "string"
          },
          "refreshExpiresIn": {
            "description": "Seconds the refresh token stays valid",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "PublicAuthSession": {
        "description": "A new customer session",
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/PublicAuthTokens"
          },
          {
            "properties": {
              "customer": {
                "$ref": "#/components/schemas/PublicAuthCustomer"
              }
            },
            "type": "object"
          }
        ]
      },
      "PublicAuthCodeSent": {
        "properties": {
          "message": {
            "type": "string"
          },
          "resendAfter": {
            "description": "Seconds before another code can be sent to this address",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "PublicCheckoutContactInput": {
        "description": "The booker's contact when not signed in and without a checkoutToken: creates an unverified account, or attaches to the existing account of that address without showing or changing it (account-first, #427)",
        "required": [
          "email",
          "firstName",
          "lastName"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "phone": {
            "type": "string",
            "nullable": true
          }
        },
        "type": "object"
      },
      "PublicWaitlistParticipantInput": {
        "description": "A saved participant (`participantId`, needs a verified signed-in customer) or a new one by name",
        "properties": {
          "participantId": {
            "type": "string",
            "format": "uuid"
          },
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "dateOfBirth": {
            "type": "string",
            "format": "date",
            "nullable": true
          }
        },
        "type": "object"
      },
      "PublicWaitlistJoinedEntry": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "enum": [
              "session",
              "series"
            ]
          },
          "sessionId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "seriesId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "status": {
            "type": "string",
            "example": "waiting"
          },
          "place": {
            "description": "Place in line",
            "type": "integer"
          },
          "participantFirstName": {
            "description": "Null for a party",
            "type": "string",
            "nullable": true
          },
          "seats": {
            "description": "Party only: seats it waits for",
            "type": "integer"
          },
          "partySummary": {
            "description": "Party only, e.g. \"2 × Adult, 1 × Child\"",
            "type": "string",
            "nullable": true
          },
          "tickets": {
            "description": "Party only: the tickets it waits for",
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        },
        "type": "object"
      },
      "PublicFormPaymentOptions": {
        "properties": {
          "online": {
            "description": "An active payment gateway",
            "type": "boolean"
          },
          "onSite": {
            "type": "boolean"
          },
          "bankTransfer": {
            "description": "Only with an IBAN set",
            "type": "boolean"
          },
          "paymentDeadlineDays": {
            "type": "integer",
            "nullable": true
          }
        },
        "type": "object"
      },
      "PublicFormOfflinePayment": {
        "description": "How to pay later",
        "properties": {
          "method": {
            "type": "string",
            "enum": [
              "on_site",
              "bank_transfer"
            ]
          },
          "reference": {
            "description": "Payment reference to mention",
            "type": "string",
            "nullable": true
          },
          "dueAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "iban": {
            "description": "Bank transfer only",
            "type": "string",
            "nullable": true
          },
          "bic": {
            "type": "string",
            "nullable": true
          },
          "accountHolder": {
            "type": "string",
            "nullable": true
          }
        },
        "type": "object"
      },
      "PublicFormAmount": {
        "properties": {
          "amount": {
            "description": "Minor units (cents)",
            "type": "integer"
          },
          "currency": {
            "type": "string",
            "example": "EUR"
          }
        },
        "type": "object"
      },
      "PublicSpotsMap": {
        "description": "A room's spots with their status; never who sits there",
        "properties": {
          "layout": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "columns": {
                "type": "integer"
              },
              "rows": {
                "type": "integer"
              }
            },
            "type": "object"
          },
          "selection": {
            "description": "`customer`: the customer picks a free spot (checkout participants[].spotId)",
            "type": "string",
            "enum": [
              "auto",
              "customer"
            ]
          },
          "spots": {
            "type": "array",
            "items": {
              "properties": {
                "id": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "type": {
                  "type": "string"
                },
                "x": {
                  "type": "integer"
                },
                "y": {
                  "type": "integer"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "free",
                    "taken",
                    "blocked"
                  ]
                }
              },
              "type": "object"
            }
          },
          "freeCount": {
            "type": "integer"
          }
        },
        "type": "object"
      },
      "PublicReviewList": {
        "description": "`shown` false (and nothing else) when the organisation keeps reviews private",
        "properties": {
          "shown": {
            "type": "boolean"
          },
          "average": {
            "description": "Average rating (1-5) of the published reviews",
            "type": "number",
            "nullable": true
          },
          "count": {
            "type": "integer"
          },
          "reviews": {
            "type": "array",
            "items": {
              "properties": {
                "id": {
                  "type": "string"
                },
                "rating": {
                  "type": "integer",
                  "maximum": 5,
                  "minimum": 1
                },
                "comment": {
                  "type": "string",
                  "nullable": true
                },
                "author": {
                  "description": "First name and initial",
                  "type": "string"
                },
                "reply": {
                  "description": "The organisation's reply",
                  "type": "string",
                  "nullable": true
                },
                "repliedAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                },
                "createdAt": {
                  "type": "string",
                  "format": "date-time"
                }
              },
              "type": "object"
            }
          }
        },
        "type": "object"
      },
      "PublicFormCustomField": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "entityType": {
            "description": "What it is asked for, e.g. customer or participant",
            "type": "string"
          },
          "fieldName": {
            "description": "Key to send the answer under",
            "type": "string"
          },
          "fieldType": {
            "description": "e.g. text, textarea, number, date, select, checkbox, phone",
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "isRequired": {
            "type": "boolean"
          },
          "sortOrder": {
            "type": "integer"
          }
        },
        "type": "object"
      },
      "PublicFormBookingQuestion": {
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/PublicFormCustomField"
          },
          {
            "properties": {
              "description": {
                "description": "E.g. the text of a consent",
                "type": "string",
                "nullable": true
              },
              "options": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "nullable": true
              },
              "perParticipant": {
                "description": "Asked per participant, else once per booking",
                "type": "boolean"
              },
              "version": {
                "type": "integer"
              }
            },
            "type": "object"
          }
        ]
      },
      "PublicFormMoney": {
        "properties": {
          "amount": {
            "description": "Minor units (cents)",
            "type": "integer"
          },
          "currency": {
            "type": "string",
            "example": "EUR"
          },
          "formatted": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "PublicSeriesDetail": {
        "description": "A series on offer. The seat counts (availableSpots, bookedCount, sessionsWithCapacity, requiredSessionsWithCapacity) are left out unless the organisation shows spots left.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "activityName": {
            "type": "string"
          },
          "requiresBillingDetails": {
            "type": "boolean"
          },
          "locationId": {
            "type": "string",
            "format": "uuid"
          },
          "locationName": {
            "type": "string"
          },
          "seriesPrice": {
            "$ref": "#/components/schemas/PublicFormMoney"
          },
          "administrativePrice": {
            "$ref": "#/components/schemas/PublicFormMoney"
          },
          "capacity": {
            "type": "integer"
          },
          "spotSelection": {
            "type": "string",
            "nullable": true,
            "enum": [
              "auto",
              "customer"
            ]
          },
          "freelyBookable": {
            "description": "Its sessions can be booked one by one",
            "type": "boolean"
          },
          "allowOverbooking": {
            "type": "boolean"
          },
          "status": {
            "type": "string"
          },
          "sessionCount": {
            "type": "integer"
          },
          "bookedCount": {
            "type": "integer"
          },
          "availableSpots": {
            "type": "integer"
          },
          "sessionsWithCapacity": {
            "type": "integer"
          },
          "requiredSessionsWithCapacity": {
            "type": "integer",
            "nullable": true
          },
          "isBookable": {
            "type": "boolean"
          },
          "isFull": {
            "description": "Full: join the waiting list",
            "type": "boolean"
          },
          "lateSignUp": {
            "description": "Started already (#340): whether and at what price it can still be joined",
            "type": "object"
          },
          "bookingWindow": {
            "$ref": "#/components/schemas/PublicFormBookingWindow"
          },
          "minimumParticipants": {
            "type": "integer",
            "nullable": true
          },
          "trialOffer": {
            "properties": {
              "price": {
                "$ref": "#/components/schemas/PublicFormAmount"
              }
            },
            "type": "object",
            "nullable": true
          },
          "startDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "endDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "firstSessionEnd": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "coachNames": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "sessionDates": {
            "description": "Sessions still to come, not cancelled",
            "type": "array",
            "items": {
              "properties": {
                "startTime": {
                  "type": "string",
                  "format": "date-time"
                },
                "endTime": {
                  "type": "string",
                  "format": "date-time"
                }
              },
              "type": "object"
            }
          }
        },
        "type": "object"
      },
      "PublicFormBookingWindow": {
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "open",
              "priority",
              "not_yet_open",
              "closed"
            ]
          },
          "opensAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "priorityOpensAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "closesAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "PublicPlan": {
        "description": "A pass or subscription on sale (#375, #376); no sale counts or tax details",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "subscription",
              "pass"
            ]
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "price": {
            "$ref": "#/components/schemas/PublicFormAmount"
          },
          "services": {
            "type": "array",
            "items": {
              "properties": {
                "activityId": {
                  "type": "string",
                  "format": "uuid"
                },
                "rule": {
                  "description": "unlimited, limited(count, per week|month|validity) or credits(cost)",
                  "type": "object"
                },
                "activityName": {
                  "type": "string",
                  "nullable": true
                }
              },
              "type": "object"
            }
          },
          "credits": {
            "description": "Credits of a pass",
            "type": "integer",
            "nullable": true
          },
          "duration": {
            "properties": {
              "count": {
                "type": "integer",
                "nullable": true
              },
              "unit": {
                "type": "string",
                "nullable": true
              },
              "endsAt": {
                "description": "A fixed end date",
                "type": "string",
                "format": "date",
                "nullable": true
              }
            },
            "type": "object"
          },
          "fixedStartDate": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "availableUntil": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "validFrom": {
            "description": "Bought today: valid from",
            "type": "string",
            "format": "date"
          },
          "validUntil": {
            "description": "Bought today: valid until",
            "type": "string",
            "format": "date"
          },
          "remaining": {
            "description": "Left to sell; null without a maximum",
            "type": "integer",
            "nullable": true
          }
        },
        "type": "object"
      },
      "PublicBooking": {
        "required": [
          "id",
          "status",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string"
          },
          "totalAmount": {
            "$ref": "#/components/schemas/Money"
          },
          "participants": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BookingParticipant"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "PublicLocation": {
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "example": "Main Studio"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "address": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "nullable": true
          }
        },
        "type": "object"
      },
      "PublicOrganisation": {
        "required": [
          "id",
          "name",
          "slug"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "example": "Yoga Studio Amsterdam"
          },
          "slug": {
            "type": "string",
            "example": "yoga-studio-amsterdam"
          },
          "timezone": {
            "description": "IANA timezone sessions happen in; the booking site shows days and times in it (#882)",
            "type": "string",
            "example": "Europe/Brussels"
          },
          "settings": {
            "properties": {
              "default_language": {
                "type": "string",
                "example": "nl"
              }
            },
            "type": "object"
          },
          "branding": {
            "description": "Colours, logo, favicon, app icon and sign-in background of the booking page",
            "properties": {
              "primary_color": {
                "type": "string",
                "example": "#1976d2",
                "nullable": true
              },
              "primary_light_color": {
                "type": "string",
                "example": "#e3f2fd",
                "nullable": true
              },
              "accent_color": {
                "type": "string",
                "example": "#ff4081",
                "nullable": true
              },
              "logo_url": {
                "type": "string",
                "format": "uri",
                "nullable": true
              },
              "favicon_url": {
                "type": "string",
                "format": "uri",
                "nullable": true
              },
              "app_icon_url": {
                "type": "string",
                "format": "uri",
                "nullable": true
              },
              "background_url": {
                "description": "Full-page WebP background of the sign-in screens (#644)",
                "type": "string",
                "format": "uri",
                "nullable": true
              }
            },
            "type": "object"
          },
          "filters": {
            "description": "Which browsing filters make sense (#842)",
            "properties": {
              "age": {
                "description": "A published activity has an age range (headcount activities never count)",
                "type": "boolean",
                "example": true
              }
            },
            "type": "object"
          },
          "checkout": {
            "description": "What the booking form asks for",
            "properties": {
              "phoneRequired": {
                "type": "boolean",
                "example": true
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "PublicSeries": {
        "required": [
          "id",
          "name",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "price": {
            "$ref": "#/components/schemas/Money"
          },
          "capacity": {
            "type": "integer"
          },
          "spotsAvailable": {
            "type": "integer"
          },
          "status": {
            "type": "string"
          },
          "sessions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicSession"
            }
          }
        },
        "type": "object"
      },
      "PublicSession": {
        "required": [
          "id",
          "startsAt",
          "endsAt",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "startsAt": {
            "type": "string",
            "format": "date-time"
          },
          "endsAt": {
            "type": "string",
            "format": "date-time"
          },
          "price": {
            "$ref": "#/components/schemas/Money"
          },
          "capacity": {
            "type": "integer"
          },
          "spotsAvailable": {
            "type": "integer"
          },
          "status": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "PublicTag": {
        "required": [
          "id",
          "name",
          "slug"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "organisationId": {
            "type": "string",
            "format": "uuid"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "ReportResult": {
        "description": "A report as a table: `columns` in order, `rows` keyed by column. Money columns hold minor units in the row's `currency`; times are local \"Y-m-d H:i\" in `timezone`.",
        "properties": {
          "report": {
            "type": "string",
            "example": "attendance"
          },
          "groupBy": {
            "type": "string",
            "example": "activity",
            "nullable": true
          },
          "period": {
            "properties": {
              "from": {
                "type": "string",
                "format": "date"
              },
              "to": {
                "type": "string",
                "format": "date"
              }
            },
            "type": "object"
          },
          "timezone": {
            "type": "string",
            "example": "Europe/Brussels"
          },
          "columns": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "moneyColumns": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "rows": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": {
                "nullable": true,
                "oneOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "number"
                  },
                  {
                    "type": "boolean"
                  }
                ]
              }
            }
          }
        },
        "type": "object"
      },
      "AnalyticsPeriod": {
        "properties": {
          "from": {
            "type": "string",
            "format": "date"
          },
          "to": {
            "type": "string",
            "format": "date"
          }
        },
        "type": "object"
      },
      "AnalyticsRevenue": {
        "description": "Money in minor units. amount = received; outstanding = still to pay; paidWithGiftCard = paid with gift cards sold before; refunded; booked = all of it; sources splits what was received (bookings, plans, giftCardSales, fees, hires)",
        "properties": {
          "currency": {
            "type": "string"
          },
          "amount": {
            "type": "integer"
          },
          "previousAmount": {
            "type": "integer"
          },
          "outstanding": {
            "type": "integer"
          },
          "booked": {
            "type": "integer"
          },
          "paidWithGiftCard": {
            "type": "integer"
          },
          "refunded": {
            "type": "integer"
          },
          "sources": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          }
        },
        "type": "object"
      },
      "AnalyticsRevenueEntry": {
        "properties": {
          "amount": {
            "description": "Received",
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "outstanding": {
            "type": "integer"
          },
          "paidWithGiftCard": {
            "type": "integer"
          }
        },
        "type": "object"
      },
      "StaffSeries": {
        "description": "A series as staff see it",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "locationId": {
            "type": "string",
            "format": "uuid"
          },
          "coachIds": {
            "description": "Default coaches of its sessions",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "seriesPrice": {
            "$ref": "#/components/schemas/Money"
          },
          "administrativePrice": {
            "$ref": "#/components/schemas/Money"
          },
          "taxCategoryId": {
            "type": "string",
            "nullable": true
          },
          "taxRate": {
            "description": "Basis points",
            "type": "integer",
            "nullable": true
          },
          "capacity": {
            "description": "Ignored when a spot layout is set: capacity then comes from the layout",
            "type": "integer"
          },
          "spotLayoutId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "spotSelection": {
            "type": "string",
            "nullable": true,
            "enum": [
              "auto",
              "customer"
            ]
          },
          "spaceIds": {
            "description": "Spaces its sessions need",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "freelyBookable": {
            "description": "Its sessions can also be booked one by one",
            "type": "boolean"
          },
          "allowOverbooking": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "active",
              "completed",
              "cancelled"
            ]
          },
          "startDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "endDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "cancellationPolicy": {
            "description": "Own cancellation policy; null follows the activity's",
            "type": "object",
            "nullable": true
          },
          "translations": {
            "description": "Name and description in other languages: {\"fr\": {\"name\": \"…\", \"description\": \"…\"}}",
            "type": "object"
          },
          "trialOffer": {
            "description": "Own trial class offer; null follows the activity's",
            "type": "object",
            "nullable": true
          },
          "ticketPrices": {
            "description": "Headcount activities: ticket type id => {amount, currency}",
            "type": "object"
          },
          "bookingWindow": {
            "description": "Own booking window; null follows the booking rules",
            "type": "object",
            "nullable": true
          },
          "lateSignUp": {
            "description": "Late sign-up overrides; null follows the booking rules",
            "type": "object",
            "nullable": true
          },
          "perks": {
            "description": "Service rules granted with a booking of this series",
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "minimumParticipants": {
            "type": "object",
            "nullable": true
          },
          "minimumCheck": {
            "type": "object",
            "nullable": true
          },
          "copiedFromSeriesId": {
            "description": "Set when copied to a new term",
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffSeriesInput": {
        "description": "Creating also needs activityId and locationId. Assigning or changing coaches needs series:write:all.",
        "required": [
          "name",
          "seriesPrice",
          "administrativePrice",
          "capacity"
        ],
        "properties": {
          "activityId": {
            "description": "Create only",
            "type": "string",
            "format": "uuid"
          },
          "locationId": {
            "description": "Create only",
            "type": "string",
            "format": "uuid"
          },
          "coachIds": {
            "description": "Left out or null: unchanged (none on create)",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "nullable": true
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "seriesPrice": {
            "$ref": "#/components/schemas/SeriesMoneyInput"
          },
          "administrativePrice": {
            "$ref": "#/components/schemas/SeriesMoneyInput"
          },
          "capacity": {
            "type": "integer"
          },
          "freelyBookable": {
            "type": "boolean",
            "default": false
          },
          "allowOverbooking": {
            "type": "boolean",
            "default": false
          },
          "taxCategoryId": {
            "type": "string",
            "nullable": true
          },
          "taxRate": {
            "description": "Basis points",
            "type": "integer",
            "nullable": true
          },
          "translations": {
            "description": "{\"fr\": {\"name\": \"…\", \"description\": \"…\"}}; left out = unchanged, {} clears",
            "type": "object"
          }
        },
        "type": "object"
      },
      "SeriesMoneyInput": {
        "required": [
          "amount",
          "currency"
        ],
        "properties": {
          "amount": {
            "description": "Smallest currency unit (cents)",
            "type": "integer",
            "example": 12000
          },
          "currency": {
            "description": "ISO 4217",
            "type": "string",
            "example": "EUR"
          }
        },
        "type": "object"
      },
      "StaffSeriesSession": {
        "description": "A session of a series",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "organisationId": {
            "type": "string",
            "format": "uuid"
          },
          "seriesId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "locationId": {
            "type": "string",
            "format": "uuid"
          },
          "coachIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "coachesOverridden": {
            "description": "Has its own coaches instead of the series'",
            "type": "boolean"
          },
          "startTime": {
            "type": "string",
            "format": "date-time"
          },
          "endTime": {
            "type": "string",
            "format": "date-time"
          },
          "capacity": {
            "type": "integer"
          },
          "bookedCount": {
            "description": "Active bookings",
            "type": "integer"
          },
          "spaceIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "closed": {
            "description": "Closed by a hire or block in a space it needs",
            "type": "boolean"
          },
          "price": {
            "$ref": "#/components/schemas/Money"
          },
          "taxCategoryId": {
            "type": "string",
            "nullable": true
          },
          "taxRate": {
            "type": "integer",
            "nullable": true
          },
          "status": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "SeriesGenerateSessionsInput": {
        "description": "Either occurrences or endDate is required.",
        "required": [
          "firstSessionStartTime",
          "firstSessionEndTime",
          "pattern"
        ],
        "properties": {
          "firstSessionStartTime": {
            "type": "string",
            "format": "date-time"
          },
          "firstSessionEndTime": {
            "type": "string",
            "format": "date-time"
          },
          "sessionPrice": {
            "$ref": "#/components/schemas/SeriesMoneyInput"
          },
          "pattern": {
            "type": "string",
            "enum": [
              "daily",
              "weekly",
              "biweekly",
              "monthly"
            ]
          },
          "daysOfWeek": {
            "description": "Weekly/biweekly: ISO weekdays, 1 = Monday",
            "type": "array",
            "items": {
              "type": "integer",
              "maximum": 7,
              "minimum": 1
            },
            "nullable": true
          },
          "occurrences": {
            "type": "integer",
            "nullable": true
          },
          "endDate": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "taxCategoryId": {
            "type": "string",
            "nullable": true
          },
          "taxRate": {
            "type": "integer",
            "nullable": true
          },
          "timezone": {
            "description": "IANA timezone in which dates and wall-clock times are evaluated",
            "type": "string",
            "nullable": true
          },
          "skipClosures": {
            "description": "Skip dates inside closure periods of the series' location",
            "type": "boolean",
            "default": true
          },
          "excludedDates": {
            "description": "Further dates to skip (YYYY-MM-DD)",
            "type": "array",
            "items": {
              "type": "string",
              "format": "date"
            }
          }
        },
        "type": "object"
      },
      "SeriesSkippedDate": {
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "startTime": {
            "type": "string",
            "format": "date-time"
          },
          "reason": {
            "type": "string"
          },
          "closurePeriodId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "closurePeriodName": {
            "type": "string",
            "nullable": true
          }
        },
        "type": "object"
      },
      "SeriesSpaceConflict": {
        "description": "A space or staff member is taken",
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "example": "space_conflict"
          },
          "reason": {
            "type": "string",
            "enum": [
              "space_occupied",
              "sessions_have_bookings",
              "appointments_booked"
            ]
          },
          "conflicts": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        },
        "type": "object"
      },
      "SeriesSettingsFee": {
        "description": "A fee: {type: fixed, amount} (cents, in the currency of the booked item) or {type: percentage, percentage} (of the price)",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "fixed",
              "percentage"
            ]
          },
          "amount": {
            "description": "Cents, with type fixed",
            "type": "integer"
          },
          "percentage": {
            "description": "With type percentage",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "SeriesSettingsCancellationPolicy": {
        "required": [
          "freeCancellationHours",
          "lateCancellation"
        ],
        "properties": {
          "freeCancellationHours": {
            "description": "Customers cancel for free until this many hours before the start",
            "type": "integer"
          },
          "lateCancellation": {
            "description": "What a cancellation after that does",
            "type": "string",
            "enum": [
              "not_allowed",
              "no_refund",
              "partial_refund"
            ]
          },
          "lateRefundPercentage": {
            "description": "Refund share with partial_refund",
            "type": "integer"
          },
          "lateCancellationFee": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/SeriesSettingsFee"
              }
            ],
            "nullable": true
          },
          "noShowFee": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/SeriesSettingsFee"
              }
            ],
            "nullable": true,
            "description": "For a series: % of the price per session"
          },
          "entitlementBookings": {
            "description": "Bookings made with a pass/subscription: lose the use (forfeit) or get it back and pay the fee",
            "type": "string",
            "default": "forfeit",
            "enum": [
              "forfeit",
              "fee"
            ]
          }
        },
        "type": "object"
      },
      "SeriesSettingsBookingWindow": {
        "description": "When the series can be booked online; all null follows the organisation's booking rules",
        "properties": {
          "publicOpensAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "priorityOpensAt": {
            "description": "Priority booking opens earlier for the priority audience",
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "priorityAudience": {
            "description": "Required with priorityOpensAt",
            "type": "string",
            "nullable": true,
            "enum": [
              "previous_participants",
              "manual"
            ]
          },
          "priorityParticipantIds": {
            "description": "With audience manual (at least one)",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "closesHoursBefore": {
            "description": "Online booking closes this many hours before the start",
            "type": "integer",
            "nullable": true
          }
        },
        "type": "object"
      },
      "SeriesSettingsBookingWindowState": {
        "description": "The booking window in effect now",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "open",
              "priority",
              "not_yet_open",
              "closed"
            ]
          },
          "opensAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "priorityOpensAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "closesAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "SeriesSettingsLateSignUp": {
        "properties": {
          "override": {
            "description": "This series' own rule; null follows the booking rules",
            "properties": {
              "allowed": {
                "type": "boolean"
              },
              "untilSession": {
                "type": "integer"
              },
              "pricing": {
                "type": "string",
                "enum": [
                  "pro_rated",
                  "full"
                ]
              }
            },
            "type": "object",
            "nullable": true
          },
          "effective": {
            "description": "The rule that applies",
            "properties": {
              "allowed": {
                "type": "boolean"
              },
              "untilSession": {
                "description": "Sign-up allowed until this session number has started",
                "type": "integer",
                "nullable": true
              },
              "pricing": {
                "type": "string",
                "enum": [
                  "pro_rated",
                  "full"
                ]
              }
            },
            "type": "object"
          },
          "state": {
            "description": "What it means now",
            "properties": {
              "started": {
                "type": "boolean"
              },
              "bookable": {
                "type": "boolean"
              },
              "reason": {
                "type": "string",
                "enum": [
                  "not_started",
                  "allowed",
                  "not_allowed",
                  "too_late",
                  "ended"
                ]
              },
              "sessionsTotal": {
                "type": "integer"
              },
              "sessionsRemaining": {
                "type": "integer"
              },
              "price": {
                "properties": {
                  "amount": {
                    "type": "integer"
                  },
                  "currency": {
                    "type": "string"
                  }
                },
                "type": "object"
              },
              "fullPrice": {
                "properties": {
                  "amount": {
                    "type": "integer"
                  },
                  "currency": {
                    "type": "string"
                  }
                },
                "type": "object"
              },
              "joinsAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "pricing": {
                "type": "string",
                "enum": [
                  "pro_rated",
                  "full"
                ]
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "SeriesSettingsPerkRule": {
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "unlimited",
              "limited"
            ]
          },
          "count": {
            "description": "With limited",
            "type": "integer"
          },
          "per": {
            "description": "With limited; validity = the whole term",
            "type": "string",
            "enum": [
              "week",
              "month",
              "validity"
            ]
          }
        },
        "type": "object"
      },
      "SeriesSettingsPerk": {
        "properties": {
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "rule": {
            "$ref": "#/components/schemas/SeriesSettingsPerkRule"
          },
          "activityName": {
            "type": "string",
            "nullable": true
          }
        },
        "type": "object"
      },
      "SeriesSettingsSpots": {
        "properties": {
          "seriesId": {
            "type": "string",
            "format": "uuid"
          },
          "spotLayoutId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "spotSelection": {
            "type": "string",
            "nullable": true,
            "enum": [
              "auto",
              "customer"
            ]
          },
          "capacity": {
            "description": "With a layout: its number of spots",
            "type": "integer"
          },
          "map": {
            "description": "Spots taken in any session to come; null without a layout",
            "properties": {
              "layout": {
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string"
                  },
                  "columns": {
                    "type": "integer"
                  },
                  "rows": {
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "selection": {
                "type": "string",
                "enum": [
                  "auto",
                  "customer"
                ]
              },
              "spots": {
                "type": "array",
                "items": {
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "type": {
                      "type": "string"
                    },
                    "x": {
                      "type": "integer"
                    },
                    "y": {
                      "type": "integer"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "free",
                        "taken"
                      ]
                    }
                  },
                  "type": "object"
                }
              },
              "freeCount": {
                "type": "integer"
              }
            },
            "type": "object",
            "nullable": true
          }
        },
        "type": "object"
      },
      "SeriesSettingsTicketPrices": {
        "description": "Ticket type id => {amount (cents), currency}",
        "type": "object",
        "additionalProperties": {
          "properties": {
            "amount": {
              "type": "integer"
            },
            "currency": {
              "type": "string"
            }
          },
          "type": "object"
        }
      },
      "SeriesTermCopyInput": {
        "required": [
          "seriesIds",
          "termStart"
        ],
        "properties": {
          "seriesIds": {
            "description": "The series to copy",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "termStart": {
            "description": "First day of the new term (YYYY-MM-DD); sessions move by whole weeks",
            "type": "string",
            "format": "date"
          },
          "timezone": {
            "description": "IANA timezone the local times are kept in",
            "type": "string",
            "nullable": true
          },
          "keepSessionCount": {
            "description": "Add a session after the last one of its slot for every date skipped",
            "type": "boolean",
            "default": false
          },
          "excludedDates": {
            "description": "Dates to skip besides the closure periods",
            "type": "array",
            "items": {
              "type": "string",
              "format": "date"
            }
          },
          "names": {
            "description": "Copy only: source series id => name of the copy",
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "activate": {
            "description": "Copy only: publish the copies (otherwise drafts)",
            "type": "boolean",
            "default": false
          },
          "reenrolment": {
            "description": "Copy only: hold a seat for each participant with a confirmed booking of the source (needs activate)",
            "properties": {
              "enabled": {
                "type": "boolean",
                "default": false
              },
              "holdDays": {
                "type": "integer",
                "default": 7
              },
              "carryOverWaitlist": {
                "type": "boolean",
                "default": false
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "SeriesTermSkippedDate": {
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "startTime": {
            "type": "string",
            "format": "date-time"
          },
          "closurePeriodName": {
            "type": "string",
            "nullable": true
          }
        },
        "type": "object"
      },
      "SeriesTermCopyPreview": {
        "properties": {
          "seriesId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "capacity": {
            "type": "integer"
          },
          "status": {
            "type": "string"
          },
          "participants": {
            "description": "Participants who would get a re-enrolment offer",
            "type": "integer"
          },
          "weeks": {
            "description": "Whole weeks every session moves",
            "type": "integer"
          },
          "sessions": {
            "type": "array",
            "items": {
              "properties": {
                "sourceSessionId": {
                  "type": "string",
                  "format": "uuid",
                  "nullable": true
                },
                "startTime": {
                  "type": "string",
                  "format": "date-time"
                },
                "endTime": {
                  "type": "string",
                  "format": "date-time"
                },
                "addedAtEnd": {
                  "description": "Added after the last session of its slot for a skipped date",
                  "type": "boolean"
                }
              },
              "type": "object"
            }
          },
          "skipped": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/SeriesTermSkippedDate"
                },
                {
                  "properties": {
                    "reason": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              ]
            }
          },
          "error": {
            "description": "Why this series can't be copied to that term",
            "type": "string",
            "nullable": true
          }
        },
        "type": "object"
      },
      "SeriesTermCopied": {
        "properties": {
          "id": {
            "description": "The copy",
            "type": "string",
            "format": "uuid"
          },
          "sourceSeriesId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "description": "draft unless activated",
            "type": "string"
          },
          "sessions": {
            "type": "integer"
          },
          "skipped": {
            "type": "integer"
          },
          "offers": {
            "description": "Re-enrolment seats held",
            "type": "integer"
          },
          "waiting": {
            "description": "Families queued on the waiting list (no seat left)",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "SeriesReenrolmentOverview": {
        "properties": {
          "seriesId": {
            "type": "string",
            "format": "uuid"
          },
          "sourceSeries": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              }
            },
            "type": "object",
            "nullable": true
          },
          "offers": {
            "type": "array",
            "items": {
              "properties": {
                "entryId": {
                  "type": "string",
                  "format": "uuid"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "reenrolled",
                    "pending",
                    "waiting",
                    "declined",
                    "lapsed"
                  ]
                },
                "participantId": {
                  "type": "string",
                  "format": "uuid"
                },
                "participantName": {
                  "type": "string",
                  "nullable": true
                },
                "customerId": {
                  "type": "string",
                  "format": "uuid",
                  "nullable": true
                },
                "customerName": {
                  "type": "string",
                  "nullable": true
                },
                "customerEmail": {
                  "type": "string",
                  "nullable": true
                },
                "heldUntil": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                },
                "offeredAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                },
                "remindedAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                },
                "bookingId": {
                  "type": "string",
                  "format": "uuid",
                  "nullable": true
                }
              },
              "type": "object"
            }
          },
          "summary": {
            "properties": {
              "reenrolled": {
                "type": "integer"
              },
              "pending": {
                "type": "integer"
              },
              "waiting": {
                "type": "integer"
              },
              "declined": {
                "type": "integer"
              },
              "lapsed": {
                "type": "integer"
              },
              "newBookings": {
                "description": "Series bookings made without an offer",
                "type": "integer"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "SeriesExtensionInput": {
        "required": [
          "mode",
          "count"
        ],
        "properties": {
          "mode": {
            "description": "weeks: repeat the last week N more weeks; sessions: add N sessions at the series' cadence",
            "type": "string",
            "enum": [
              "weeks",
              "sessions"
            ]
          },
          "count": {
            "type": "integer"
          },
          "timezone": {
            "type": "string",
            "nullable": true
          }
        },
        "type": "object"
      },
      "SeriesAttendanceTotals": {
        "properties": {
          "present": {
            "type": "integer"
          },
          "absent": {
            "type": "integer"
          },
          "excused": {
            "type": "integer"
          },
          "unmarked": {
            "type": "integer"
          },
          "rate": {
            "description": "present / (present + absent), null when nothing is marked",
            "type": "number",
            "format": "float",
            "nullable": true
          }
        },
        "type": "object"
      },
      "SeriesAttendanceRegister": {
        "properties": {
          "seriesId": {
            "type": "string",
            "format": "uuid"
          },
          "sessions": {
            "description": "Sessions not cancelled, in time order",
            "type": "array",
            "items": {
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "startTime": {
                  "type": "string",
                  "format": "date-time"
                },
                "endTime": {
                  "type": "string",
                  "format": "date-time"
                },
                "status": {
                  "type": "string"
                },
                "totals": {
                  "$ref": "#/components/schemas/SeriesAttendanceTotals"
                }
              },
              "type": "object"
            }
          },
          "participants": {
            "type": "array",
            "items": {
              "properties": {
                "participant": {
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "firstName": {
                      "type": "string"
                    },
                    "lastName": {
                      "type": "string"
                    },
                    "fullName": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                },
                "attendance": {
                  "description": "Session id => present | absent | excused | unmarked",
                  "type": "object",
                  "additionalProperties": {
                    "type": "string",
                    "enum": [
                      "present",
                      "absent",
                      "excused",
                      "unmarked"
                    ]
                  }
                },
                "totals": {
                  "$ref": "#/components/schemas/SeriesAttendanceTotals"
                }
              },
              "type": "object"
            }
          },
          "parties": {
            "description": "Headcount bookings (#798): one row per party at its one session, counted in people",
            "type": "array",
            "items": {
              "properties": {
                "party": {
                  "properties": {
                    "bookingId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "size": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                },
                "attendance": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                },
                "totals": {
                  "$ref": "#/components/schemas/SeriesAttendanceTotals"
                }
              },
              "type": "object"
            }
          },
          "totals": {
            "$ref": "#/components/schemas/SeriesAttendanceTotals"
          }
        },
        "type": "object"
      },
      "SeriesAttendeeMessage": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "subject": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "reason": {
            "type": "string",
            "enum": [
              "manual",
              "session_changed",
              "coach_changed"
            ]
          },
          "includeWaitlist": {
            "type": "boolean"
          },
          "recipientCount": {
            "type": "integer"
          },
          "sentBy": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "delivery": {
            "properties": {
              "queued": {
                "type": "integer"
              },
              "sent": {
                "type": "integer"
              },
              "delivered": {
                "type": "integer"
              },
              "failed": {
                "type": "integer"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "SeriesWaitlistEntry": {
        "description": "A waiting list entry as staff see it",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "participantId": {
            "description": "null for a party",
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "participantName": {
            "type": "string",
            "nullable": true
          },
          "partySummary": {
            "description": "A party's tickets, no names (#796)",
            "type": "string",
            "nullable": true
          },
          "seats": {
            "type": "integer"
          },
          "type": {
            "type": "string",
            "enum": [
              "session",
              "series"
            ]
          },
          "sessionId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "seriesId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "waiting",
              "notified",
              "converted",
              "expired",
              "cancelled"
            ]
          },
          "origin": {
            "type": "string",
            "enum": [
              "waitlist",
              "reenrolment"
            ]
          },
          "position": {
            "type": "integer"
          },
          "notifiedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "convertedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "bookingId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "SessionMoney": {
        "description": "An amount in the smallest currency unit (cents) with its currency",
        "required": [
          "amount",
          "currency"
        ],
        "properties": {
          "amount": {
            "type": "integer",
            "example": 1500
          },
          "currency": {
            "type": "string",
            "example": "EUR"
          }
        },
        "type": "object"
      },
      "StaffSession": {
        "description": "One session (an occurrence of an activity at a date, time and location), as staff see it",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "activityId": {
            "type": "string",
            "format": "uuid"
          },
          "locationId": {
            "type": "string",
            "format": "uuid"
          },
          "coachIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "bookingWindow": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/SessionBookingWindow"
              }
            ],
            "nullable": true,
            "description": "Its own booking window; null follows its series, then the organisation's rules"
          },
          "minimumParticipants": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/SessionMinimumParticipantsRule"
              }
            ],
            "nullable": true
          },
          "minimumCheck": {
            "description": "The outcome of the minimum check, once made",
            "properties": {
              "checkedAt": {
                "type": "string",
                "format": "date-time"
              },
              "outcome": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "coachesOverridden": {
            "description": "Coaches differ from the series' coaches",
            "type": "boolean"
          },
          "seriesId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "startTime": {
            "type": "string",
            "format": "date-time"
          },
          "endTime": {
            "type": "string",
            "format": "date-time"
          },
          "capacity": {
            "type": "integer"
          },
          "spotLayoutId": {
            "description": "Assigned spots: capacity then comes from the layout",
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "spotSelection": {
            "type": "string",
            "nullable": true,
            "enum": [
              "auto",
              "customer"
            ]
          },
          "blockedSpotIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "spaceIds": {
            "description": "Spaces it needs",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "closed": {
            "description": "Closed by a hire or staff block of a space it needs",
            "type": "boolean"
          },
          "closedByOccupationId": {
            "type": "string",
            "nullable": true
          },
          "origin": {
            "description": "`slot` for an appointment booked at a free moment (can't be moved or get another coach)",
            "type": "string"
          },
          "slotOfferId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "bookedCount": {
            "description": "Seats taken by active bookings (a party counts its seats)",
            "type": "integer"
          },
          "availableSpots": {
            "type": "integer"
          },
          "ticketPrices": {
            "description": "Headcount activities: ticket type id => {amount, currency}",
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/SessionMoney"
            }
          },
          "price": {
            "properties": {
              "amount": {
                "type": "integer"
              },
              "currency": {
                "type": "string"
              },
              "formatted": {
                "type": "string",
                "example": "€15.00"
              }
            },
            "type": "object"
          },
          "taxCategoryId": {
            "type": "string",
            "nullable": true
          },
          "taxRate": {
            "description": "Basis points",
            "type": "integer",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "scheduled",
              "completed",
              "cancelled"
            ]
          },
          "isBookable": {
            "type": "boolean"
          },
          "isFull": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "StaffSessionListItem": {
        "description": "A session in the list, with its waiting list count and, for an appointment, who it is with",
        "allOf": [
          {
            "$ref": "#/components/schemas/StaffSession"
          },
          {
            "properties": {
              "waitlistCount": {
                "description": "People waiting; null without waitlist:read",
                "type": "integer",
                "nullable": true
              },
              "appointment": {
                "description": "Only for appointments (origin slot)",
                "properties": {
                  "staffName": {
                    "type": "string",
                    "nullable": true
                  },
                  "attendees": {
                    "description": "Null without participant:read or customer:read",
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "nullable": true
                  }
                },
                "type": "object"
              }
            },
            "type": "object"
          }
        ]
      },
      "StaffSessionInput": {
        "description": "Create (activityId and locationId required) or update a session. Times ISO 8601.",
        "required": [
          "startTime",
          "endTime",
          "capacity",
          "price"
        ],
        "properties": {
          "activityId": {
            "description": "Create only",
            "type": "string",
            "format": "uuid"
          },
          "locationId": {
            "description": "Required on create; on update moves the session",
            "type": "string",
            "format": "uuid"
          },
          "seriesId": {
            "description": "Create only",
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "startTime": {
            "type": "string",
            "format": "date-time"
          },
          "endTime": {
            "type": "string",
            "format": "date-time"
          },
          "capacity": {
            "type": "integer"
          },
          "price": {
            "$ref": "#/components/schemas/SessionMoney"
          },
          "coachIds": {
            "description": "Assigning or changing coaches needs session:write:all (else 403)",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "nullable": true
          },
          "spaceIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "nullable": true
          },
          "taxCategoryId": {
            "type": "string",
            "nullable": true
          },
          "taxRate": {
            "description": "Basis points",
            "type": "integer",
            "nullable": true
          },
          "notifyAttendees": {
            "description": "Update only: email the attendees about a changed time or place",
            "type": "boolean",
            "default": false
          },
          "locale": {
            "description": "Update only: language of the emails (default: Accept-Language)",
            "type": "string"
          }
        },
        "type": "object"
      },
      "SessionSpaceConflict": {
        "description": "A space or staff member is taken then (code space_conflict)",
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "example": "space_conflict"
          },
          "reason": {
            "type": "string",
            "enum": [
              "space_occupied",
              "sessions_have_bookings",
              "appointments_booked"
            ]
          },
          "conflicts": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        },
        "type": "object"
      },
      "SessionCancelInput": {
        "properties": {
          "notify": {
            "description": "Email the attendees",
            "type": "boolean",
            "default": true
          },
          "cancelBookings": {
            "type": "boolean",
            "default": false
          },
          "refund": {
            "description": "Refund paid bookings through the gateway",
            "type": "boolean",
            "default": false
          },
          "reason": {
            "type": "string",
            "nullable": true,
            "maxLength": 500
          },
          "credit": {
            "description": "Give what was paid back as account credit instead of a refund (needs payment:write, else 403 code forbidden)",
            "type": "boolean",
            "default": false
          },
          "creditExpiresOn": {
            "description": "Last day the credit can be used",
            "type": "string",
            "format": "date",
            "nullable": true
          }
        },
        "type": "object"
      },
      "SessionBatchResult": {
        "properties": {
          "done": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "skipped": {
            "type": "array",
            "items": {
              "properties": {
                "sessionId": {
                  "type": "string",
                  "format": "uuid"
                },
                "reason": {
                  "description": "not_found, not_scheduled, capacity_below_bookings, or another refusal",
                  "type": "string"
                }
              },
              "type": "object"
            }
          }
        },
        "type": "object"
      },
      "SessionTag": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "SessionBookingRow": {
        "description": "A confirmed booking of the session: a participant, or a party (participant null)",
        "properties": {
          "bookingId": {
            "type": "string",
            "format": "uuid"
          },
          "bookingStatus": {
            "type": "string",
            "example": "confirmed"
          },
          "bookedAt": {
            "type": "string",
            "format": "date-time"
          },
          "participant": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "firstName": {
                "type": "string"
              },
              "lastName": {
                "type": "string"
              },
              "fullName": {
                "type": "string"
              }
            },
            "type": "object",
            "nullable": true
          },
          "partyContact": {
            "properties": {
              "firstName": {
                "type": "string"
              },
              "lastName": {
                "type": "string"
              },
              "phone": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "partySize": {
            "type": "integer",
            "nullable": true
          },
          "seats": {
            "type": "integer"
          },
          "lines": {
            "description": "A party's ticket and extra lines with unit price and total",
            "type": "array",
            "items": {
              "type": "object"
            },
            "nullable": true
          }
        },
        "type": "object"
      },
      "SessionAttendeeMessage": {
        "properties": {
          "id": {
            "type": "string"
          },
          "subject": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "reason": {
            "type": "string",
            "enum": [
              "manual",
              "session_changed",
              "coach_changed"
            ]
          },
          "includeWaitlist": {
            "type": "boolean"
          },
          "recipientCount": {
            "type": "integer"
          },
          "sentBy": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "delivery": {
            "properties": {
              "queued": {
                "type": "integer"
              },
              "sent": {
                "type": "integer"
              },
              "delivered": {
                "type": "integer"
              },
              "failed": {
                "type": "integer"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "SessionCoachSubstitution": {
        "properties": {
          "id": {
            "type": "string"
          },
          "sessionId": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "covered",
              "withdrawn",
              "dismissed"
            ]
          },
          "note": {
            "type": "string",
            "nullable": true
          },
          "coach": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/SessionPerson"
              }
            ],
            "nullable": true
          },
          "requestedBy": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/SessionPerson"
              }
            ],
            "nullable": true
          },
          "requestedAt": {
            "type": "string",
            "format": "date-time"
          },
          "substitute": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/SessionPerson"
              }
            ],
            "nullable": true
          },
          "resolvedBy": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/SessionPerson"
              }
            ],
            "nullable": true
          },
          "resolvedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "SessionPerson": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "fullName": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "SessionRosterAttendee": {
        "description": "One register row: a participant, or a party (participant null). What is filled in depends on the caller's permissions: coaches never see contacts or booking details.",
        "properties": {
          "party": {
            "properties": {
              "bookingId": {
                "type": "string",
                "format": "uuid"
              },
              "firstName": {
                "type": "string"
              },
              "lastName": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "size": {
                "type": "integer"
              },
              "seats": {
                "type": "integer"
              },
              "lines": {
                "type": "array",
                "items": {
                  "properties": {
                    "type": {
                      "type": "string",
                      "enum": [
                        "ticket",
                        "extra"
                      ]
                    },
                    "ticketTypeId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "extraId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "quantity": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              },
              "phone": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "participant": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "firstName": {
                "type": "string"
              },
              "lastName": {
                "type": "string"
              },
              "fullName": {
                "type": "string"
              },
              "dateOfBirth": {
                "type": "string",
                "format": "date",
                "nullable": true
              },
              "remarks": {
                "type": "string",
                "nullable": true
              },
              "internalNotes": {
                "type": "string",
                "nullable": true
              },
              "customFields": {
                "type": "array",
                "items": {
                  "properties": {
                    "fieldName": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "fieldType": {
                      "type": "string"
                    },
                    "value": {}
                  },
                  "type": "object"
                }
              },
              "answers": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              }
            },
            "type": "object",
            "nullable": true
          },
          "booking": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "type": {
                "type": "string",
                "enum": [
                  "session",
                  "series"
                ]
              },
              "status": {
                "type": "string"
              },
              "bookedAt": {
                "type": "string",
                "format": "date-time"
              },
              "holdExpiresAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "isTrial": {
                "type": "boolean"
              }
            },
            "type": "object",
            "nullable": true
          },
          "contacts": {
            "type": "array",
            "items": {
              "properties": {
                "customerId": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "email": {
                  "type": "string"
                },
                "phone": {
                  "type": "string",
                  "nullable": true
                }
              },
              "type": "object"
            }
          },
          "checkedIn": {
            "type": "boolean"
          },
          "checkedInAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "attendance": {
            "type": "string",
            "enum": [
              "present",
              "absent",
              "excused",
              "unmarked"
            ]
          },
          "absence": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/SessionAbsence"
              }
            ],
            "nullable": true
          },
          "spot": {
            "properties": {
              "id": {
                "type": "string"
              },
              "label": {
                "type": "string"
              },
              "type": {
                "type": "string"
              }
            },
            "type": "object",
            "nullable": true
          },
          "spotBookingId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          }
        },
        "type": "object"
      },
      "SessionAbsence": {
        "properties": {
          "id": {
            "type": "string"
          },
          "sessionId": {
            "type": "string",
            "format": "uuid"
          },
          "participantId": {
            "type": "string",
            "format": "uuid"
          },
          "bookingId": {
            "type": "string",
            "format": "uuid"
          },
          "reportedBy": {
            "description": "customer or staff",
            "type": "string"
          },
          "reason": {
            "type": "string",
            "nullable": true
          },
          "reportedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "SessionCheckIn": {
        "description": "An attendance mark: for a participant, or for a party (participantId null, bookingId set)",
        "properties": {
          "participantId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "bookingId": {
            "description": "Parties only",
            "type": "string",
            "format": "uuid"
          },
          "checkedIn": {
            "type": "boolean"
          },
          "checkedInAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "attendance": {
            "type": "string",
            "enum": [
              "present",
              "absent",
              "unmarked"
            ]
          }
        },
        "type": "object"
      },
      "SessionCodeCheckIn": {
        "properties": {
          "participantId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "participantName": {
            "type": "string"
          },
          "bookingId": {
            "description": "Parties only",
            "type": "string",
            "format": "uuid"
          },
          "partySize": {
            "description": "Parties only",
            "type": "integer"
          },
          "checkedIn": {
            "type": "boolean"
          },
          "checkedInAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "attendance": {
            "type": "string",
            "nullable": true
          },
          "alreadyCheckedIn": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "SessionWalkIn": {
        "properties": {
          "bookingId": {
            "type": "string",
            "format": "uuid"
          },
          "bookingStatus": {
            "type": "string"
          },
          "participantId": {
            "type": "string",
            "format": "uuid"
          },
          "price": {
            "$ref": "#/components/schemas/SessionMoney"
          },
          "checkedIn": {
            "type": "boolean"
          },
          "checkedInAt": {
            "type": "string",
            "format": "date-time"
          },
          "attendance": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "SessionIncident": {
        "properties": {
          "id": {
            "type": "string"
          },
          "session": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "startTime": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "activityName": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object"
          },
          "participant": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "fullName": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object"
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time"
          },
          "description": {
            "type": "string"
          },
          "injury": {
            "type": "string",
            "nullable": true
          },
          "actionsTaken": {
            "type": "string",
            "nullable": true
          },
          "peopleInformed": {
            "type": "string",
            "nullable": true
          },
          "parentsInformed": {
            "type": "boolean"
          },
          "parentsInformedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "parentAcknowledged": {
            "type": "boolean"
          },
          "parentAcknowledgedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "parentAcknowledgedBy": {
            "type": "string",
            "nullable": true
          },
          "reportedBy": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "updatedBy": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "SessionIncidentInput": {
        "required": [
          "participantId",
          "occurredAt",
          "description"
        ],
        "properties": {
          "participantId": {
            "type": "string",
            "format": "uuid"
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time"
          },
          "description": {
            "type": "string"
          },
          "injury": {
            "type": "string",
            "nullable": true
          },
          "actionsTaken": {
            "type": "string",
            "nullable": true
          },
          "peopleInformed": {
            "type": "string",
            "nullable": true
          },
          "parentsInformed": {
            "type": "boolean",
            "default": false
          },
          "parentsInformedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "parentAcknowledged": {
            "type": "boolean",
            "default": false
          },
          "parentAcknowledgedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "parentAcknowledgedBy": {
            "type": "string",
            "nullable": true
          }
        },
        "type": "object"
      },
      "SessionSpots": {
        "description": "Who sits where at a session with assigned spots",
        "properties": {
          "sessionId": {
            "type": "string",
            "format": "uuid"
          },
          "spotLayoutId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "spotSelection": {
            "type": "string",
            "nullable": true,
            "enum": [
              "auto",
              "customer"
            ]
          },
          "blockedSpotIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "capacity": {
            "type": "integer"
          },
          "map": {
            "description": "Null without a layout",
            "properties": {
              "layout": {
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string"
                  },
                  "columns": {
                    "type": "integer"
                  },
                  "rows": {
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "selection": {
                "type": "string",
                "enum": [
                  "auto",
                  "customer"
                ]
              },
              "spots": {
                "description": "Each spot (id, label, type, x, y) with status free|taken|blocked and the attendee sitting there",
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "unassigned": {
                "description": "Attendees without a spot",
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "blockedSpotIds": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            },
            "type": "object",
            "nullable": true
          }
        },
        "type": "object"
      },
      "SessionBookingWindow": {
        "description": "When a session can be booked online, with optional earlier priority booking",
        "properties": {
          "publicOpensAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "priorityOpensAt": {
            "description": "Must be before publicOpensAt",
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "priorityAudience": {
            "type": "string",
            "nullable": true,
            "enum": [
              "previous_participants",
              "manual"
            ]
          },
          "priorityParticipantIds": {
            "description": "For audience manual",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "closesHoursBefore": {
            "type": "integer",
            "nullable": true,
            "maximum": 1440,
            "minimum": 0
          }
        },
        "type": "object"
      },
      "SessionMinimumParticipantsRule": {
        "properties": {
          "minimum": {
            "type": "integer"
          },
          "decisionHoursBefore": {
            "description": "When the minimum is checked",
            "type": "integer"
          },
          "autoCancel": {
            "description": "Cancel automatically when not reached",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "SessionWaitlistEntry": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "participantId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "participantName": {
            "type": "string",
            "nullable": true
          },
          "partySummary": {
            "description": "A party's tickets, e.g. \"2 × Adult, 1 × Child\"",
            "type": "string",
            "nullable": true
          },
          "seats": {
            "type": "integer"
          },
          "type": {
            "type": "string",
            "enum": [
              "session",
              "series"
            ]
          },
          "sessionId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "seriesId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "waiting",
              "notified",
              "converted",
              "expired",
              "cancelled"
            ]
          },
          "origin": {
            "type": "string",
            "enum": [
              "waitlist",
              "reenrolment"
            ]
          },
          "position": {
            "type": "integer"
          },
          "notifiedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "convertedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "bookingId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "SessionPagination": {
        "properties": {
          "total": {
            "type": "integer"
          },
          "per_page": {
            "type": "integer"
          },
          "current_page": {
            "type": "integer"
          },
          "total_pages": {
            "type": "integer"
          },
          "has_more": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "TeamDomainError": {
        "description": "A refused change: the message, its type and, for some, a stable code",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "type": {
            "description": "e.g. role_rule_violation, membership_locations, invitation_already_member",
            "type": "string",
            "example": "role_rule_violation"
          },
          "code": {
            "description": "Stable error code, where the error has one",
            "type": "string"
          }
        },
        "type": "object"
      },
      "TeamRoleRef": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "example": "Coach"
          },
          "system": {
            "description": "The locked Admin role",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "TeamMembership": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "userId": {
            "type": "string",
            "format": "uuid"
          },
          "organisationId": {
            "type": "string",
            "format": "uuid"
          },
          "roleId": {
            "type": "string",
            "format": "uuid"
          },
          "role": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/TeamRoleRef"
              }
            ],
            "nullable": true
          },
          "locationIds": {
            "description": "The locations the member is limited to (#938); empty when not limited",
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "locationRestricted": {
            "description": "False: every location",
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "TeamUser": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "isActive": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "TeamMember": {
        "properties": {
          "membership": {
            "$ref": "#/components/schemas/TeamMembership"
          },
          "user": {
            "$ref": "#/components/schemas/TeamUser"
          }
        },
        "type": "object"
      },
      "TeamInvitation": {
        "description": "An open invitation to join the team with a role (#372)",
        "properties": {
          "id": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "role": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/TeamRoleRef"
              }
            ],
            "nullable": true
          },
          "invitedBy": {
            "description": "User id of the member who invited (left out in the invite answer)",
            "type": "string",
            "format": "uuid"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "sentAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "expired": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "TeamRole": {
        "description": "A role: a set of permissions `resource:action:scope` (#373)",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "organisationId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "system": {
            "description": "The locked Admin role: never changed or deleted",
            "type": "boolean"
          },
          "template": {
            "description": "The template it was made from",
            "type": "string",
            "nullable": true,
            "enum": [
              "admin",
              "manager",
              "coach",
              "front_desk"
            ]
          },
          "permissions": {
            "type": "array",
            "items": {
              "type": "string",
              "example": "session:read:assigned"
            }
          },
          "memberCount": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "TeamRoleInput": {
        "required": [
          "name",
          "permissions"
        ],
        "properties": {
          "name": {
            "description": "Unique within the organisation",
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "permissions": {
            "description": "Permission strings from GET /permissions, `resource:action:scope`",
            "type": "array",
            "items": {
              "type": "string",
              "example": "attendance:write:assigned"
            }
          }
        },
        "type": "object"
      },
      "TeamPermissionGroup": {
        "properties": {
          "group": {
            "type": "string"
          },
          "permissions": {
            "type": "array",
            "items": {
              "properties": {
                "key": {
                  "type": "string",
                  "example": "session:read"
                },
                "resource": {
                  "type": "string"
                },
                "action": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "scopes": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "enum": [
                      "all",
                      "assigned"
                    ]
                  }
                },
                "locationBinding": {
                  "description": "How the permission relates to locations (#938)",
                  "type": "string",
                  "enum": [
                    "bound",
                    "derived",
                    "org"
                  ]
                },
                "locationRestricted": {
                  "description": "What a member limited to locations keeps of it",
                  "type": "string",
                  "enum": [
                    "location",
                    "org_wide",
                    "own",
                    "none"
                  ]
                }
              },
              "type": "object"
            }
          }
        },
        "type": "object"
      },
      "TeamCalendarFeed": {
        "properties": {
          "feed": {
            "description": "Null when the member has no feed",
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/CustomerCalendarFeedLinks"
              },
              {
                "properties": {
                  "createdAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "wholeSchedule": {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/CustomerCalendarFeedLinks"
                      }
                    ],
                    "nullable": true,
                    "description": "The organisation's whole schedule (?scope=all), only for members with session:read:all"
                  }
                },
                "type": "object"
              }
            ]
          },
          "publicSchedule": {
            "$ref": "#/components/schemas/CustomerCalendarFeedLinks"
          }
        },
        "type": "object"
      },
      "TeamNotificationSettings": {
        "description": "The member's notification emails for this organisation (#735)",
        "properties": {
          "categories": {
            "description": "The categories the member's role can hear about",
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "bookings",
                "cancellations",
                "waitlist",
                "payments",
                "fees",
                "hires"
              ]
            }
          },
          "preferences": {
            "description": "Category => off | instant | digest",
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "enum": [
                "off",
                "instant",
                "digest"
              ]
            }
          },
          "defaults": {
            "description": "True while nothing is stored: the role's defaults apply",
            "type": "boolean"
          },
          "digestHour": {
            "description": "The daily digest goes out from this hour, organisation time",
            "type": "integer",
            "example": 7
          }
        },
        "type": "object"
      },
      "TermsAndConditions": {
        "required": [
          "id",
          "versionNumber",
          "title",
          "content",
          "effectiveFrom"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "versionNumber": {
            "type": "integer"
          },
          "title": {
            "type": "string"
          },
          "content": {
            "type": "string"
          },
          "effectiveFrom": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "ValidationErrorResponse": {
        "description": "Validation error response with field-level errors",
        "required": [
          "error",
          "errors"
        ],
        "properties": {
          "error": {
            "description": "General error message",
            "type": "string",
            "example": "Validation failed"
          },
          "errors": {
            "description": "Field-level validation errors",
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        },
        "type": "object"
      },
      "WaitlistEntry": {
        "required": [
          "id",
          "type",
          "status",
          "position",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string"
          },
          "sessionId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "seriesId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "status": {
            "type": "string"
          },
          "position": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "Webhook": {
        "description": "An endpoint the organisation's events are posted to, signed with HMAC-SHA256 in the Plek-Signature header",
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventName"
            }
          },
          "active": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "type": "object"
      },
      "WebhookEventName": {
        "type": "string",
        "enum": [
          "booking.confirmed",
          "booking.cancelled",
          "customer.created",
          "payment.paid",
          "payment.refunded",
          "session.cancelled",
          "session.coach_changed",
          "fee.created",
          "hire.requested",
          "hire.quoted",
          "hire.accepted",
          "hire.confirmed",
          "hire.declined",
          "hire.expired",
          "hire.cancelled"
        ]
      },
      "WebhookInput": {
        "required": [
          "url",
          "events"
        ],
        "properties": {
          "url": {
            "description": "Where events are posted (https)",
            "type": "string",
            "format": "uri"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventName"
            }
          },
          "active": {
            "description": "Only on update",
            "type": "boolean",
            "default": true
          }
        },
        "type": "object"
      },
      "WebhookDelivery": {
        "properties": {
          "id": {
            "type": "string"
          },
          "eventId": {
            "type": "string"
          },
          "event": {
            "description": "An event name, or ping for a test",
            "type": "string",
            "example": "booking.confirmed"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "succeeded",
              "failed"
            ]
          },
          "attempts": {
            "type": "integer"
          },
          "maxAttempts": {
            "type": "integer"
          },
          "responseStatus": {
            "description": "The HTTP status the endpoint answered",
            "type": "integer",
            "nullable": true
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastAttemptAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "nextAttemptAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "payload": {
            "description": "Only in the delivery log and on redelivery",
            "type": "object"
          },
          "responseBody": {
            "description": "Only in the delivery log and on redelivery",
            "type": "string",
            "nullable": true
          }
        },
        "type": "object"
      },
      "OAuthConnection": {
        "description": "An app a member connected over OAuth (#815), e.g. an AI assistant using the MCP server",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "clientName": {
            "type": "string"
          },
          "clientHost": {
            "description": "Host of the client id URL (Client ID Metadata Document), null for a registered client",
            "type": "string",
            "nullable": true
          },
          "redirectHost": {
            "type": "string"
          },
          "organisation": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "plek.read",
                "plek.write"
              ]
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastUsedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "member": {
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "nullable": true
              },
              "email": {
                "type": "string",
                "nullable": true
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "WidgetFlow": {
        "description": "What the widget shows",
        "type": "string",
        "enum": [
          "booking",
          "catalogue",
          "activity",
          "agenda",
          "button",
          "gift-cards",
          "request"
        ]
      },
      "WidgetConfig": {
        "description": "The widget's settings, in six groups. Unknown keys are refused (422), keys left out take their default, so the format can grow. Ids in `scope` must be the organisation's (422 unknown_reference); `behaviour.handoffUrl` must be on one of its allowed websites.",
        "properties": {
          "scope": {
            "properties": {
              "activityIds": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                }
              },
              "locationIds": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                }
              },
              "categoryIds": {
                "description": "Tag ids",
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                }
              },
              "seriesId": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              },
              "sessionId": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              },
              "mode": {
                "type": "string",
                "default": "both",
                "enum": [
                  "session",
                  "series",
                  "both"
                ]
              },
              "start": {
                "type": "string",
                "default": "activities",
                "enum": [
                  "activities",
                  "locations"
                ]
              }
            },
            "type": "object"
          },
          "show": {
            "properties": {
              "spotsLeft": {
                "type": "string",
                "enum": [
                  "always",
                  "low",
                  "never"
                ]
              },
              "shortDescription": {
                "type": "boolean"
              },
              "price": {
                "type": "boolean"
              },
              "images": {
                "type": "boolean"
              },
              "ageRange": {
                "type": "boolean"
              },
              "location": {
                "type": "boolean"
              },
              "teacher": {
                "type": "boolean"
              },
              "nextDate": {
                "type": "boolean"
              },
              "filters": {
                "type": "boolean"
              }
            },
            "type": "object"
          },
          "layout": {
            "properties": {
              "view": {
                "type": "string",
                "enum": [
                  "grid",
                  "list",
                  "week"
                ]
              },
              "columns": {
                "type": "integer",
                "maximum": 4,
                "minimum": 1
              },
              "limit": {
                "type": "integer",
                "nullable": true,
                "maximum": 100,
                "minimum": 1
              },
              "daysAhead": {
                "type": "integer",
                "maximum": 90,
                "minimum": 1
              },
              "maxDates": {
                "type": "integer",
                "maximum": 50,
                "minimum": 1
              },
              "hideHeader": {
                "type": "boolean"
              },
              "agendaView": {
                "type": "string",
                "default": "list",
                "enum": [
                  "list",
                  "week",
                  "month"
                ]
              },
              "agendaViews": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "list",
                    "week",
                    "month"
                  ]
                }
              }
            },
            "type": "object"
          },
          "look": {
            "properties": {
              "theme": {
                "type": "string",
                "enum": [
                  "branding",
                  "custom"
                ]
              },
              "accent": {
                "description": "A hex colour",
                "type": "string",
                "example": "#1e88e5",
                "nullable": true
              },
              "font": {
                "type": "string",
                "enum": [
                  "inherit",
                  "system",
                  "plek"
                ]
              },
              "radius": {
                "type": "string",
                "enum": [
                  "square",
                  "soft",
                  "round"
                ]
              },
              "density": {
                "type": "string",
                "enum": [
                  "compact",
                  "comfortable"
                ]
              },
              "scheme": {
                "type": "string",
                "enum": [
                  "light",
                  "dark",
                  "auto"
                ]
              },
              "background": {
                "type": "string",
                "enum": [
                  "transparent",
                  "surface"
                ]
              },
              "buttonStyle": {
                "type": "string",
                "enum": [
                  "solid",
                  "outline",
                  "link"
                ]
              }
            },
            "type": "object"
          },
          "texts": {
            "description": "Per text, per language (en, nl, fr, de): own wording or null",
            "properties": {
              "buttonLabel": {
                "type": "object",
                "additionalProperties": {
                  "type": "string",
                  "nullable": true
                }
              },
              "emptyText": {
                "type": "object",
                "additionalProperties": {
                  "type": "string",
                  "nullable": true
                }
              }
            },
            "type": "object"
          },
          "behaviour": {
            "properties": {
              "history": {
                "type": "string",
                "enum": [
                  "push",
                  "replace",
                  "none"
                ]
              },
              "handoff": {
                "type": "string",
                "enum": [
                  "inline",
                  "page",
                  "site"
                ]
              },
              "handoffUrl": {
                "type": "string",
                "format": "uri",
                "nullable": true
              },
              "checkout": {
                "type": "string",
                "enum": [
                  "top",
                  "newTab"
                ]
              },
              "language": {
                "description": "page, browser, or fixed:en|nl|fr|de",
                "type": "string",
                "example": "page"
              },
              "scrollOffset": {
                "type": "integer",
                "maximum": 400,
                "minimum": 0
              },
              "minHeight": {
                "type": "integer",
                "maximum": 2000,
                "minimum": 0
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "WidgetPreset": {
        "properties": {
          "id": {
            "description": "wgt_ + 8 base62 characters, used in the snippet",
            "type": "string",
            "example": "wgt_a1B2c3D4"
          },
          "name": {
            "type": "string"
          },
          "flow": {
            "$ref": "#/components/schemas/WidgetFlow"
          },
          "version": {
            "description": "Send it back on update (optimistic locking)",
            "type": "integer"
          },
          "config": {
            "$ref": "#/components/schemas/WidgetConfig"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object"
      },
      "WidgetPresetError": {
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "enum": [
              "validation_failed",
              "not_found",
              "limit_reached",
              "stale_version",
              "unknown_reference"
            ]
          },
          "errors": {
            "description": "Field path (e.g. config.layout.columns) => message",
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        },
        "type": "object"
      }
    },
    "responses": {
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "BadRequest": {
        "description": "Bad request - invalid input",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "ValidationError": {
        "description": "Validation error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationErrorResponse"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Authentication required",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Access denied",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "InternalError": {
        "description": "Internal server error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "CustomerEmailNotVerified": {
        "description": "The customer has not confirmed their email address yet (`code: email_not_verified`). Unverified customers (accounts made by the booking form) may book and pay, but not see stored participants, booking history, balances or other stored data until they sign in with an emailed code or link.",
        "content": {
          "application/json": {
            "schema": {
              "properties": {
                "error": {
                  "type": "string"
                },
                "code": {
                  "type": "string",
                  "example": "email_not_verified"
                }
              },
              "type": "object"
            }
          }
        }
      },
      "ExportFile": {
        "description": "The file (Content-Disposition attachment; X-Export-Rows has the number of rows). Headers are in the request language; amounts are decimals.",
        "headers": {
          "X-Export-Rows": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "text/csv": {
            "schema": {
              "type": "string",
              "format": "binary"
            }
          },
          "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
            "schema": {
              "type": "string",
              "format": "binary"
            }
          }
        }
      },
      "PublicAuthTooManyAttempts": {
        "description": "Too many attempts from this IP address or for this email address; `Retry-After` says when to try again",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "properties": {
                "error": {
                  "type": "string"
                },
                "retryAfter": {
                  "type": "integer"
                }
              },
              "type": "object"
            }
          }
        }
      }
    },
    "parameters": {
      "OrgSlug": {
        "name": "org_slug",
        "in": "path",
        "description": "Organisation slug identifier",
        "required": true,
        "schema": {
          "type": "string",
          "example": "my-organisation"
        }
      },
      "OrganisationId": {
        "name": "organisation_id",
        "in": "path",
        "description": "Organisation UUID (staff and API key routes)",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "ResourceId": {
        "name": "id",
        "in": "path",
        "description": "Resource UUID",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "ExportFormat": {
        "name": "format",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string",
          "default": "csv",
          "enum": [
            "csv",
            "xlsx"
          ]
        }
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "description": "A staff access token (the admin app's sign-in), or an API key sent as a bearer token.",
        "bearerFormat": "JWT",
        "scheme": "bearer"
      },
      "apiKeyAuth": {
        "type": "apiKey",
        "description": "Organisation API key (plek_live_...), also accepted as \"Authorization: Bearer plek_live_...\". Only for /api/org/{organisation_id} routes of its own organisation, with the permissions of its role.",
        "name": "X-Api-Key",
        "in": "header"
      },
      "customerAuth": {
        "type": "http",
        "description": "A customer access token (JWT with the customer audience), sent as `Authorization: Bearer ...`. A customer\ngets it from the sign-in routes under `/api/public/{org_slug}/auth/...`: an emailed 6-digit code\n(`/auth/code` + `/auth/code/verify`), a magic link (`/auth/link`), a password (`/auth/login`), or Google /\nMicrosoft (`/auth/oidc/...`); `/auth/refresh` renews it. Customers are separate from staff: a token is bound\nto the one organisation it was issued for (the `org_slug` must match, else 401) and never works on staff\nroutes, nor a staff token here. A deactivated account gets 401.",
        "bearerFormat": "JWT",
        "scheme": "bearer"
      }
    }
  },
  "tags": [
    {
      "name": "Public - Organisation",
      "description": "Public endpoints for organisation information"
    },
    {
      "name": "Public - Activities",
      "description": "Public endpoints for browsing activities"
    },
    {
      "name": "Public - Locations",
      "description": "Public endpoints for browsing locations"
    },
    {
      "name": "Public - Sessions",
      "description": "Public endpoints for browsing available sessions"
    },
    {
      "name": "Public - Series",
      "description": "Public endpoints for browsing available series"
    },
    {
      "name": "Public - Bookings",
      "description": "Public endpoints for creating and managing bookings"
    },
    {
      "name": "Public - Waitlist",
      "description": "Public endpoints for joining waitlists"
    },
    {
      "name": "Public - Gift Cards",
      "description": "Public endpoints for gift card purchases and validation"
    },
    {
      "name": "Public - Discount Codes",
      "description": "Public endpoints for validating discount codes"
    },
    {
      "name": "Public - Calendar",
      "description": "Public endpoints for calendar exports"
    },
    {
      "name": "Public - Terms",
      "description": "Public endpoints for terms and conditions"
    },
    {
      "name": "Public - Tags",
      "description": "Public endpoints for browsing tags"
    },
    {
      "name": "Public - Checkout",
      "description": "Booking: quote and place a checkout (sessions, series, parties and appointment moments)"
    },
    {
      "name": "Activities",
      "description": "What an organisation offers (staff): activities, images, ticket types, attendee mode, booking style, extras"
    },
    {
      "name": "Sessions",
      "description": "Single occurrences of an activity (staff): schedule, cancel, roster, check-in, spots, waiting list"
    },
    {
      "name": "Bookings",
      "description": "Bookings of sessions, series and parties (staff): list, book, cancel, reschedule, payments, receipts"
    },
    {
      "name": "Customers",
      "description": "Customer accounts (staff): list, create, edit, notes, tasks, tags, account credit, personal data"
    },
    {
      "name": "Participants",
      "description": "The people who attend (staff): list, edit, duplicates and merging, attendance, incidents"
    },
    {
      "name": "Appointments",
      "description": "Services booked at a free moment (#924): slot offers, providers, staff booking, affected appointments"
    },
    {
      "name": "API keys",
      "description": "Organisation API keys (#404) and one-hour test keys for the developer portal (#931). Staff only: an API key can never manage keys."
    },
    {
      "name": "Bookkeeping",
      "description": "The organisation's bookkeeping software (Billit, Yuki): connecting it, the sales pushed to it and the sync log (#892)"
    },
    {
      "name": "Closures",
      "description": "Closure periods (school and public holidays, #393, #742): skipped when sessions are generated; cancelling the sessions inside them"
    },
    {
      "name": "Coaches",
      "description": "Coaches (staff, #741, #601): weekly availability and time off, pay rates, substitute coaches"
    },
    {
      "name": "Customer - Account",
      "description": "The signed-in customer's profile, password, email address and billing details"
    },
    {
      "name": "Customer - Participants",
      "description": "The people the customer books for"
    },
    {
      "name": "Customer - Bookings",
      "description": "The customer's bookings: details, cancelling, paying, moving, absences and make-up credits"
    },
    {
      "name": "Customer - Waitlist and interest",
      "description": "Waiting lists the customer is on and \"notify me\" requests"
    },
    {
      "name": "Customer - Reviews",
      "description": "Reviews of activities the customer attended"
    },
    {
      "name": "Customer - Plans and credit",
      "description": "Passes, subscriptions, perks, buying a plan, account credit"
    },
    {
      "name": "Customer - Open amounts",
      "description": "Late-cancellation and no-show fees to pay"
    },
    {
      "name": "Customer - Hires",
      "description": "The customer's venue hire requests, quotes and payments"
    },
    {
      "name": "Customer - Privacy",
      "description": "Personal data export and deleting the account"
    },
    {
      "name": "Customer - Calendar and push",
      "description": "Calendar feed of the customer's bookings and push notifications on their devices"
    },
    {
      "name": "Public - Customer sign-in",
      "description": "Customer sign-in: email code, magic link, password, Google / Microsoft, refresh and sign-out"
    },
    {
      "name": "Public - Booking form",
      "description": "What the booking form needs: custom fields, booking questions, booking window, cancellation policy, payment options"
    },
    {
      "name": "Public - Plans",
      "description": "Passes and subscriptions on sale"
    },
    {
      "name": "Public - Hire requests",
      "description": "Requesting a venue hire"
    },
    {
      "name": "Public - Reviews",
      "description": "Published reviews of activities"
    },
    {
      "name": "Public - Preferences",
      "description": "The opt-out and unsubscribe links of emails, and a device stopping push notifications"
    },
    {
      "name": "Gift cards",
      "description": "Gift cards sold online or created by staff, and account credit (#752)"
    },
    {
      "name": "Discount codes",
      "description": "Discount codes, typed or automatic, with order rules (#744)"
    },
    {
      "name": "Tax categories",
      "description": "VAT rates used for sale lines and receipts"
    },
    {
      "name": "Venue hire",
      "description": "Venue hire requests (staff, #794): the inbox, spaces, the quote, accepting for a client, charges, refunds, receipts and the check-in"
    },
    {
      "name": "Imports and exports",
      "description": "CSV imports of customers, participants and bookings (#396), and CSV/XLSX exports (#398)"
    },
    {
      "name": "Locations",
      "description": "Locations (staff): addresses, pictures, spaces and day parts (#792), staff blocks and the day view of the spaces, spot layouts (#496), equipment"
    },
    {
      "name": "Email and text messages",
      "description": "How the organisation emails and texts its customers (staff): email provider tests, Gmail, editable email templates, text messages (Twilio) and emails to a segment of customers"
    },
    {
      "name": "Organisation settings",
      "description": "Booking rules, cancellation policy, the onboarding checklist, terms and conditions, and privacy (retention periods and log)"
    },
    {
      "name": "Custom fields",
      "description": "Extra fields on customers, participants and bookings, and ready-made templates of them (#750)"
    },
    {
      "name": "Tags",
      "description": "Tags on sessions and series (staff), shown on the booking site for filtering"
    },
    {
      "name": "Reviews",
      "description": "Moderating customer reviews and the review settings (#355)"
    },
    {
      "name": "Payments",
      "description": "Payment gateways (incl. Mollie Connect), payments and refunds, paying later (on site, bank transfer), receipts and open fees"
    },
    {
      "name": "Plans and entitlements",
      "description": "Subscriptions and passes on sale (plans) and what customers hold: subscriptions, passes, make-up credits, series perks and manual credits (#374, #375, #376, #378)"
    },
    {
      "name": "Point of sale",
      "description": "The front desk sale screen (#614): sessions, plans and gift cards sold to a customer at once"
    },
    {
      "name": "Waiting lists",
      "description": "Everyone waiting, per session and series still to come (#733), and offering free spots"
    },
    {
      "name": "Reports and analytics",
      "description": "Staff reports (#599: attendance, no-shows, liabilities, coach hours, revenue, fees, utilisation, day totals, coach pay) and the dashboard's analytics"
    },
    {
      "name": "Series",
      "description": "Series of sessions (staff): create, schedule, settings, term rollover, attendance"
    },
    {
      "name": "Team",
      "description": "Staff members, roles and permissions, invitations, and the signed-in member's own role, schedule, calendar feed and notification emails"
    },
    {
      "name": "Search",
      "description": "The staff search box: customers, participants and bookings for one term"
    },
    {
      "name": "Integrations",
      "description": "Connecting other software (staff): outgoing webhooks, apps connected over OAuth, API usage"
    },
    {
      "name": "Widgets",
      "description": "Embeddable widgets (#659, #661): presets a website's snippet points at (staff), and what the embed loads (public)"
    }
  ]
}
