{
  "openapi": "3.1.0",
  "info": {
    "title": "Optune Calendar Protected API",
    "version": "1.7.0",
    "description": "**Changelog vs currently deployed docs ([https://api.optune.me/docs](https://api.optune.me/docs), Calendar Protected API 1.4.8).** Read-only `fee`, `feeCurrency`, `managerFee`, and `managerFeeCurrency` are calculated from the deal with `calculateTotalFee` (`fee` and `feeCurrency` from `total.artistFee`, `managerFee` and `managerFeeCurrency` from `total.totalCommission`), not read from the stored booking. They are `null` when the deal is hidden or not loaded. Writes still reject `fee`, `feeCurrency`, `managerFee`, and `managerFeeCurrency`; GET `bookings[].itinerary` is always present (unrequested lists `[]`, scalars `null`, `has*` false); sending `itinerary.tickets` replaces the list in full (v1.4.8 merged by `_id`); venue GET omits `addressLine1` (use `street` / `streetName` / `streetNumber`). Changed: every itinerary list row always includes `_id` on GET (flights, guestList, hotels, lineup, schedules, specials, tickets, transports, travels, marketing materials); POST accepts optional `_id` to keep the stored id; writing tickets, marketing materials, or `bookingCapacity` bumps event `updatedAt` (the field already existed). Added: contact `billingAddress` create, partial update, and clear (previously GET-only), `extend=marketing` (`materials` + `receiverContacts`), event `bookingCapacity`, list query `includeDeleted`, nested venue `isVerified`. Clarification: itinerary arrays including tickets are full-replace when sent; optional `_id` keeps the stored row id. Unchanged: resource `_id`s on events, bookings, contacts, and venues; contact refs `{ contactId }`; GET-only `sequenceNr`; venue GET `attachments` (same `{ category, public_id, url }` shape as event attachments; `logo` remains the primary image). — Protected group API for reading and writing artists, events, contacts, and venues. **Request and response JSON shapes differ.** Use `*WriteInput` / `Api*WriteInput` schemas for POST bodies and `Api*Item` schemas for GET list/detail — do not round-trip GET payloads into POST. GET responses always include every field of the resource shape: values (including empty string `\"\"`) are returned as stored; empty lists are `[]`; missing/`null`/`undefined` scalars are JSON `null` (keys are never omitted). GET also includes server-derived fields (ids, slugs, flags, `bookings[].deal`, denormalized names, nested `venue`, itinerary `has*` flags, read-only `sequenceNr`). Every itinerary list row always includes `_id`. POST creates resources; POST on `/:id` updates (partial payloads supported for events); DELETE soft-deletes events, contacts, and venues. Soft-deleted events are excluded from list responses unless `includeDeleted=true`/`1`/`yes`. Event list/detail support `extend` for which booking itinerary slices are populated on GET (`tickets` for ticket sales, `marketing` for Werbematerialien); unrequested slices still appear as empty lists/`null`. Itinerary lists including tickets are replaced in full when sent. POST itinerary list rows accept optional `_id` to keep the stored id. POST and GET marketing delivery contacts as `itinerary.marketing.receiverContacts`. Uncaptured ticket `sales` and capacities are JSON `null`, never `0`. `bookingCapacity` is the show capacity and is separate from `venue.capacity`. Verified locations appear only on events that use them; `GET /v1/group/venues` returns group-owned venues only."
  },
  "servers": [
    { "url": "https://api-calendar.optune.me", "description": "Production" },
    { "url": "http://localhost:3110", "description": "Local development" }
  ],
  "tags": [{ "name": "Group Protected API" }],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API Key",
        "description": "Authorization header format: `Authorization: Bearer <api-key>`"
      }
    },
    "schemas": {
      "ApiError": {
        "type": "object",
        "properties": {
          "code": { "type": "string" },
          "reason": { "type": "string" }
        },
        "required": ["code", "reason"]
      },
      "PaginationMeta": {
        "type": "object",
        "properties": {
          "hasMore": { "type": "boolean" },
          "limit": { "type": "integer" },
          "offset": { "type": "integer" },
          "total": { "type": "integer" }
        },
        "required": ["hasMore", "limit", "offset", "total"]
      },
      "ApiArtistAttachmentItem": {
        "type": "object",
        "description": "Artist file row (excludes profile picture).",
        "properties": {
          "category": { "type": "string" },
          "fileUrl": { "type": "string", "nullable": true }
        }
      },
      "ApiArtistSettings": {
        "type": "object",
        "properties": {
          "agencyCommission": { "type": "number", "nullable": true },
          "managerCommission": { "type": "number", "nullable": true },
          "otherCommission": { "type": "number", "nullable": true },
          "otherCommissionDescription": { "type": "string", "nullable": true }
        }
      },
      "ApiArtistItem": {
        "type": "object",
        "description": "**Response (GET only).** Mapped artist from list/detail endpoints. No write schema — artists are read-only via this API.",
        "properties": {
          "_id": { "type": "string" },
          "appleMusic": { "type": "string", "nullable": true },
          "attachments": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiArtistAttachmentItem" }
          },
          "backgroundColor": { "type": "string", "nullable": true },
          "biography": { "type": "string", "nullable": true },
          "description": { "type": "string", "nullable": true },
          "facebook": { "type": "string", "nullable": true },
          "genres": { "type": "array", "items": { "type": "string" } },
          "instagram": { "type": "string", "nullable": true },
          "musicLabel": { "type": "string", "nullable": true },
          "name": { "type": "string" },
          "onescreener": { "type": "string", "nullable": true },
          "otherLink": { "type": "string", "nullable": true },
          "profilePicture": { "type": "string", "nullable": true },
          "settings": {
            "anyOf": [{ "$ref": "#/components/schemas/ApiArtistSettings" }, { "type": "null" }]
          },
          "slug": { "type": "string" },
          "soundcloud": { "type": "string", "nullable": true },
          "soundLink": { "type": "string", "nullable": true },
          "spotify": { "type": "string", "nullable": true },
          "tikTok": { "type": "string", "nullable": true },
          "types": { "type": "array", "items": { "type": "string" } },
          "updatedAt": { "type": "string", "format": "date-time" },
          "version": { "type": "number" },
          "website": { "type": "string", "nullable": true },
          "x": { "type": "string", "nullable": true },
          "youTube": { "type": "string", "nullable": true }
        },
        "required": [
          "_id",
          "attachments",
          "genres",
          "name",
          "slug",
          "types",
          "updatedAt",
          "version"
        ]
      },
      "ApiBillingAddress": {
        "type": "object",
        "description": "**Response (GET).** Separate billing address (Rechnungsadresse) on `ApiContactItem.billingAddress`. Present only when a billing address is stored; otherwise the parent field is `null`. Every field is included; missing values are `null`.",
        "properties": {
          "addressLine1": { "type": "string", "nullable": true },
          "addressLine2": { "type": "string", "nullable": true },
          "city": { "type": "string", "nullable": true },
          "company": { "type": "string", "nullable": true },
          "country": { "type": "string", "nullable": true },
          "name": { "type": "string", "nullable": true },
          "postalCode": { "type": "string", "nullable": true },
          "state": {
            "type": "string",
            "nullable": true,
            "description": "Canton, state, or region."
          }
        },
        "required": [
          "addressLine1",
          "addressLine2",
          "city",
          "company",
          "country",
          "name",
          "postalCode",
          "state"
        ]
      },
      "ApiBillingAddressWrite": {
        "type": "object",
        "description": "**Request (POST).** Partial billing address. Send only fields to change. `null` clears that field. An empty string is ignored. Omit the parent `billingAddress` to leave the stored address unchanged; `billingAddress: null` removes it.",
        "properties": {
          "addressLine1": { "type": "string", "nullable": true },
          "addressLine2": { "type": "string", "nullable": true },
          "city": { "type": "string", "nullable": true },
          "company": { "type": "string", "nullable": true },
          "country": { "type": "string", "nullable": true },
          "name": { "type": "string", "nullable": true },
          "postalCode": { "type": "string", "nullable": true },
          "state": {
            "type": "string",
            "nullable": true,
            "description": "Canton, state, or region."
          }
        }
      },
      "ApiContactItem": {
        "type": "object",
        "description": "**Response (GET).** `ApiContactItem`. GET always includes every field: stored values (including empty string `\"\"`) as-is, missing scalars as `null`. Phones: `phoneNr` (primary/mobile), `phoneNrOther` (secondary/landline). `mobilePhoneNr` is **deprecated** (duplicate of `phoneNr`). `otherPhoneNr` was renamed to `phoneNrOther`. Read-only: `additionalInfo`, `additionalNotes`. `billingAddress` is writable on POST (`ApiBillingAddressWrite`); GET returns `ApiBillingAddress` or `null`. Writable on POST: see `ApiContactInput` / `ApiContactWriteInput`.",
        "properties": {
          "_id": { "type": "string", "nullable": true },
          "additionalInfo": { "type": "string", "nullable": true },
          "additionalNotes": { "type": "string", "nullable": true },
          "addressLine1": { "type": "string", "nullable": true },
          "addressLine2": { "type": "string", "nullable": true },
          "agencyId": { "type": "string", "nullable": true },
          "artistId": { "type": "string", "nullable": true },
          "billingAddress": {
            "anyOf": [{ "$ref": "#/components/schemas/ApiBillingAddress" }, { "type": "null" }],
            "description": "Separate billing address. `null` when none is stored."
          },
          "city": { "type": "string", "nullable": true },
          "company": { "type": "string", "nullable": true },
          "contactNr": { "type": "string", "nullable": true },
          "contactType": { "type": "string", "nullable": true },
          "country": { "type": "string", "nullable": true },
          "createdAt": { "type": "string", "format": "date-time", "nullable": true },
          "email": { "type": "string", "nullable": true },
          "isDeleted": { "type": "boolean" },
          "language": { "type": "string", "nullable": true },
          "mobilePhoneNr": {
            "deprecated": true,
            "type": "string",
            "nullable": true,
            "description": "**Deprecated.** Same value as `phoneNr`. Use `phoneNr` instead."
          },
          "name": { "type": "string", "nullable": true },
          "notes": { "type": "string", "nullable": true },
          "otherLink": { "type": "string", "nullable": true },
          "phoneNr": {
            "type": "string",
            "nullable": true,
            "description": "Primary phone (stored as `mobilePhoneNr`)."
          },
          "phoneNrOther": {
            "type": "string",
            "nullable": true,
            "description": "Secondary phone (stored as `phoneNr`). Replaces `otherPhoneNr`."
          },
          "position": { "type": "string", "nullable": true },
          "postalCode": { "type": "string", "nullable": true },
          "slug": { "type": "string", "nullable": true },
          "state": { "type": "string", "nullable": true },
          "updatedAt": { "type": "string", "format": "date-time", "nullable": true },
          "vatNr": { "type": "string", "nullable": true },
          "version": { "type": "number", "nullable": true },
          "website": { "type": "string", "nullable": true }
        },
        "required": [
          "_id",
          "additionalInfo",
          "additionalNotes",
          "addressLine1",
          "addressLine2",
          "agencyId",
          "artistId",
          "billingAddress",
          "city",
          "company",
          "contactNr",
          "contactType",
          "country",
          "createdAt",
          "email",
          "isDeleted",
          "language",
          "mobilePhoneNr",
          "name",
          "notes",
          "otherLink",
          "phoneNr",
          "phoneNrOther",
          "position",
          "postalCode",
          "slug",
          "state",
          "updatedAt",
          "vatNr",
          "version",
          "website"
        ]
      },
      "ApiAttachmentItem": {
        "type": "object",
        "properties": {
          "category": { "type": "string" },
          "public_id": { "type": "string" },
          "url": { "type": "string" }
        },
        "required": ["category", "public_id", "url"]
      },
      "ProtectedVenueMapMarker": {
        "type": "object",
        "properties": {
          "lat": { "type": "number" },
          "lng": { "type": "number" }
        },
        "required": ["lat", "lng"]
      },
      "ProtectedVenueMapViewport": {
        "type": "object",
        "properties": {
          "northeast": { "$ref": "#/components/schemas/ProtectedVenueMapMarker" },
          "southwest": { "$ref": "#/components/schemas/ProtectedVenueMapMarker" }
        },
        "required": ["northeast", "southwest"]
      },
      "ProtectedVenueMap": {
        "type": "object",
        "properties": {
          "marker": { "$ref": "#/components/schemas/ProtectedVenueMapMarker" },
          "viewport": { "$ref": "#/components/schemas/ProtectedVenueMapViewport" }
        },
        "required": ["marker"]
      },
      "ApiVenueItem": {
        "type": "object",
        "description": "**Response (GET).** Mapped venue (nested under `ApiEventItem.venue` or venue endpoints). Differs from `ApiVenueWriteInput` — includes map, logo, status, `isVerified`. `capacity` is an integer (≥ 0); unknown is `null`, never coerced from empty to `0`. `venueType` is always a string array of free-form labels (empty list is `[]`). Street is stored as separate `streetName` and `streetNumber`; GET also includes combined `street` (the former `addressLine1` is not returned). Venue files appear as `logo` plus `attachments` (same `{ category, public_id, url }` shape as event attachments; empty list is `[]`). On event GET the nested `venue` is complete (id, name, postal address, map, capacity, type, `venueType`, `isPublic`, `isVerified`, attachments), including Optune-verified public venues used on that booking. `isPublic` is true when visibility is `public`. `isVerified` is true only when both `verifiedAt` and `verifiedBy` are set. A private venue is usable on an event when the API key's group belongs to it. A public venue owned by another group is usable only when it is active and verified. `GET /v1/group/venues` returns venues where the group is owner, manager, or viewer — there is no verified-directory export. Venue create always stores a private venue. On event POST use `venueId`, not this object.",
        "properties": {
          "_id": { "type": "string" },
          "attachments": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiAttachmentItem" }
          },
          "capacity": { "minimum": 0, "type": "integer", "nullable": true },
          "city": { "type": "string", "nullable": true },
          "country": { "type": "string", "nullable": true },
          "description": { "type": "string", "nullable": true },
          "email": { "type": "string", "nullable": true },
          "isDeleted": { "type": "boolean" },
          "isPlace": { "type": "boolean" },
          "isPublic": {
            "type": "boolean",
            "description": "True when `visibility` is `public`. Venues created by this API are private, so this is false."
          },
          "isVerified": {
            "type": "boolean",
            "description": "True only when both `verifiedAt` and `verifiedBy` are set. A public venue owned by another group can be used on an event only when it is verified."
          },
          "locality": { "type": "string", "nullable": true },
          "localityFull": { "type": "string", "nullable": true },
          "logo": {
            "anyOf": [{ "$ref": "#/components/schemas/ApiAttachmentItem" }, { "type": "null" }]
          },
          "map": {
            "anyOf": [{ "$ref": "#/components/schemas/ProtectedVenueMap" }, { "type": "null" }]
          },
          "name": { "type": "string" },
          "phoneNr": { "type": "string", "nullable": true },
          "placeId": { "type": "string", "nullable": true },
          "postalCode": { "type": "string", "nullable": true },
          "slug": { "type": "string" },
          "status": { "type": "string", "description": "Venue status enum." },
          "street": { "type": "string", "nullable": true },
          "streetName": { "type": "string", "nullable": true },
          "streetNumber": { "type": "string", "nullable": true },
          "type": { "type": "string", "description": "Venue type enum." },
          "updatedAt": { "type": "string", "format": "date-time" },
          "venueType": {
            "type": "array",
            "items": { "type": "string" },
            "description": "Free-form venue type labels. Always present; empty list is `[]`."
          },
          "visibility": {
            "type": "string",
            "description": "`private` or `public`. Venues created by this API are `private`. `isPublic` mirrors this value."
          },
          "version": { "type": "number" },
          "website": { "type": "string", "nullable": true }
        },
        "required": [
          "_id",
          "attachments",
          "capacity",
          "city",
          "country",
          "description",
          "email",
          "isDeleted",
          "isPlace",
          "isPublic",
          "isVerified",
          "locality",
          "localityFull",
          "logo",
          "map",
          "name",
          "phoneNr",
          "placeId",
          "postalCode",
          "slug",
          "status",
          "street",
          "streetName",
          "streetNumber",
          "type",
          "updatedAt",
          "venueType",
          "visibility",
          "version",
          "website"
        ]
      },
      "ApiBookingContactItem": {
        "allOf": [
          { "$ref": "#/components/schemas/ApiContactItem" },
          {
            "type": "object",
            "description": "`ApiBookingContactItem` — same fields as `ApiContactItem` plus itinerary `category`. Used on `bookings[].contacts` and `bookings[].itinerary` contact arrays (`artistCareContacts`, `promoterContacts`, `technicalContacts`, `otherContacts`). Always present keys; empty values are `null`. Without `extend=contacts` the arrays are `[]`.",
            "properties": {
              "category": {
                "type": "string",
                "description": "Itinerary contact category (artist care, promoter, technical, other)."
              }
            },
            "required": ["category"]
          }
        ]
      },
      "ApiContactRefInput": {
        "type": "object",
        "description": "Reference an existing contact on itinerary contact lists.",
        "properties": {
          "contactId": { "type": "string" }
        },
        "required": ["contactId"]
      },
      "ApiItineraryListRowBase": {
        "type": "object",
        "description": "GET itinerary list rows always include `_id`. GET-only `sequenceNr` is not accepted on POST. Missing `sequenceNr` is `null`. Always present on GET rows. POST may send `_id` to keep the stored row id.",
        "properties": {
          "_id": {
            "type": "string",
            "nullable": true,
            "description": "Always present on GET. Optional on POST to keep the stored row id."
          },
          "sequenceNr": {
            "readOnly": true,
            "type": "number",
            "nullable": true,
            "description": "Assigned from array order on write (1, 2, 3, …)."
          }
        },
        "required": ["_id", "sequenceNr"]
      },
      "ApiBookingOperationalPeriod": {
        "type": "object",
        "description": "Operational period on a booking (UTC date/time labels). POST: `ApiBookingOperationalPeriodInput`. When this object is present, all four keys are always included (`null` if unset).",
        "properties": {
          "endDate": { "type": "string", "format": "date", "nullable": true },
          "endTime": { "type": "string", "nullable": true },
          "startDate": { "type": "string", "format": "date", "nullable": true },
          "startTime": { "type": "string", "nullable": true }
        },
        "required": ["endDate", "endTime", "startDate", "startTime"]
      },
      "ApiItineraryFlightItem": {
        "allOf": [
          { "$ref": "#/components/schemas/ApiItineraryListRowBase" },
          {
            "type": "object",
            "properties": {
              "arrivalAirport": { "type": "string", "nullable": true },
              "arrivalTime": { "type": "string", "nullable": true },
              "departureAirport": { "type": "string", "nullable": true },
              "departureTime": { "type": "string", "nullable": true },
              "flightDate": { "type": "string", "format": "date", "nullable": true },
              "flightNr": { "type": "string", "nullable": true },
              "notes": { "type": "string", "nullable": true },
              "passengerName": { "type": "string", "nullable": true },
              "reservationCode": { "type": "string", "nullable": true }
            },
            "required": [
              "arrivalAirport",
              "arrivalTime",
              "departureAirport",
              "departureTime",
              "flightDate",
              "flightNr",
              "notes",
              "passengerName",
              "reservationCode"
            ]
          }
        ]
      },
      "ApiItineraryFlightInput": {
        "type": "object",
        "description": "`ApiItineraryFlightInput` — POST row for `itinerary.flights[]`. Optional `_id` keeps the stored row id.",
        "properties": {
          "_id": {
            "type": "string",
            "description": "Optional. Keep the stored row id when replacing the list."
          },
          "arrivalAirport": { "type": "string", "nullable": true },
          "arrivalTime": { "type": "string", "nullable": true },
          "departureAirport": { "type": "string", "nullable": true },
          "departureTime": { "type": "string", "nullable": true },
          "flightDate": { "type": "string", "format": "date", "nullable": true },
          "flightNr": { "type": "string", "nullable": true },
          "notes": { "type": "string", "nullable": true },
          "passengerName": { "type": "string", "nullable": true },
          "reservationCode": { "type": "string", "nullable": true }
        }
      },
      "ApiItineraryGuestListItem": {
        "allOf": [
          { "$ref": "#/components/schemas/ApiItineraryListRowBase" },
          {
            "type": "object",
            "properties": {
              "addedAt": { "type": "string", "format": "date-time", "nullable": true },
              "addedBy": { "type": "string", "nullable": true },
              "comment": { "type": "string", "nullable": true },
              "name": { "type": "string", "nullable": true },
              "spotCount": { "type": "number", "nullable": true }
            },
            "required": ["addedAt", "addedBy", "comment", "name", "spotCount"]
          }
        ]
      },
      "ApiItineraryGuestListInput": {
        "type": "object",
        "description": "`ApiItineraryGuestListInput` — POST row for `itinerary.guestList[]`. Requires `name`. Optional `_id` keeps the stored row id.",
        "properties": {
          "_id": {
            "type": "string",
            "description": "Optional. Keep the stored row id when replacing the list."
          },
          "comment": { "type": "string", "nullable": true },
          "name": { "type": "string" },
          "spotCount": { "type": "number", "nullable": true }
        },
        "required": ["name"]
      },
      "ApiItineraryHotelItem": {
        "allOf": [
          { "$ref": "#/components/schemas/ApiItineraryListRowBase" },
          {
            "type": "object",
            "properties": {
              "doubleRoom": { "type": "number", "nullable": true },
              "hotelAddress": { "type": "string", "nullable": true },
              "hotelBookingNr": { "type": "string", "nullable": true },
              "hotelCheckinDate": { "type": "string", "format": "date", "nullable": true },
              "hotelCheckinTime": { "type": "string", "nullable": true },
              "hotelCheckoutDate": { "type": "string", "format": "date", "nullable": true },
              "hotelCheckoutLate": { "type": "boolean", "nullable": true },
              "hotelCheckoutTime": { "type": "string", "nullable": true },
              "hotelEmail": { "type": "string", "nullable": true },
              "hotelName": { "type": "string", "nullable": true },
              "hotelNotes": { "type": "string", "nullable": true },
              "hotelPhoneNr": { "type": "string", "nullable": true },
              "hotelWebsite": { "type": "string", "nullable": true },
              "singleRoom": { "type": "number", "nullable": true }
            },
            "required": [
              "doubleRoom",
              "hotelAddress",
              "hotelBookingNr",
              "hotelCheckinDate",
              "hotelCheckinTime",
              "hotelCheckoutDate",
              "hotelCheckoutLate",
              "hotelCheckoutTime",
              "hotelEmail",
              "hotelName",
              "hotelNotes",
              "hotelPhoneNr",
              "hotelWebsite",
              "singleRoom"
            ]
          }
        ]
      },
      "ApiItineraryHotelInput": {
        "type": "object",
        "description": "`ApiItineraryHotelInput` — POST row for `itinerary.hotels[]`. Optional `_id` keeps the stored row id.",
        "properties": {
          "_id": {
            "type": "string",
            "description": "Optional. Keep the stored row id when replacing the list."
          },
          "doubleRoom": { "type": "number", "nullable": true },
          "hotelAddress": { "type": "string", "nullable": true },
          "hotelBookingNr": { "type": "string", "nullable": true },
          "hotelCheckinDate": { "type": "string", "format": "date", "nullable": true },
          "hotelCheckinTime": { "type": "string", "nullable": true },
          "hotelCheckoutDate": { "type": "string", "format": "date", "nullable": true },
          "hotelCheckoutLate": { "type": "boolean", "nullable": true },
          "hotelCheckoutTime": { "type": "string", "nullable": true },
          "hotelEmail": { "type": "string", "nullable": true },
          "hotelName": { "type": "string", "nullable": true },
          "hotelNotes": { "type": "string", "nullable": true },
          "hotelPhoneNr": { "type": "string", "nullable": true },
          "hotelWebsite": { "type": "string", "nullable": true },
          "singleRoom": { "type": "number", "nullable": true }
        }
      },
      "ApiItineraryLineupItem": {
        "allOf": [
          { "$ref": "#/components/schemas/ApiItineraryListRowBase" },
          {
            "type": "object",
            "properties": {
              "name": { "type": "string", "nullable": true },
              "playEndDate": { "type": "string", "format": "date", "nullable": true },
              "playEndTime": { "type": "string", "nullable": true },
              "playStartDate": { "type": "string", "format": "date", "nullable": true },
              "playStartTime": { "type": "string", "nullable": true }
            },
            "required": ["name", "playEndDate", "playEndTime", "playStartDate", "playStartTime"]
          }
        ]
      },
      "ApiItineraryLineupInput": {
        "type": "object",
        "description": "`ApiItineraryLineupInput` — POST row for `itinerary.lineup[]`. Requires `name`. Optional `_id` keeps the stored row id.",
        "properties": {
          "_id": {
            "type": "string",
            "description": "Optional. Keep the stored row id when replacing the list."
          },
          "name": { "type": "string" },
          "playEndDate": { "type": "string", "format": "date", "nullable": true },
          "playEndTime": { "type": "string", "nullable": true },
          "playStartDate": { "type": "string", "format": "date", "nullable": true },
          "playStartTime": { "type": "string", "nullable": true }
        },
        "required": ["name"]
      },
      "ApiItineraryMarketingItem": {
        "allOf": [
          { "$ref": "#/components/schemas/ApiItineraryListRowBase" },
          {
            "type": "object",
            "description": "The `value` type follows `type`: number for `count`, boolean for `choice`, string for `text`. Includes `_id`.",
            "properties": {
              "_id": {
                "type": "string",
                "nullable": true,
                "description": "Stored material id. Always present on GET."
              },
              "description": { "type": "string", "nullable": true },
              "type": { "type": "string", "nullable": true, "enum": ["choice", "count", "text"] },
              "value": {
                "nullable": true,
                "oneOf": [{ "type": "string" }, { "type": "number" }, { "type": "boolean" }]
              }
            },
            "required": ["_id", "description", "type", "value"]
          }
        ]
      },
      "ApiItineraryMarketingInput": {
        "type": "object",
        "description": "`ApiItineraryMarketingInput` — POST row for `itinerary.marketing.materials[]`. Requires `description`. Optional `_id` keeps the stored material id; omit `_id` to create a new id. The `value` type follows `type`: number for `count`, boolean for `choice`, string for `text`. `sequenceNr` is assigned from array order. The `materials` array replaces the stored list in full.",
        "properties": {
          "_id": {
            "type": "string",
            "description": "Optional. Keep the stored material id when replacing the list."
          },
          "description": { "type": "string" },
          "type": { "type": "string", "enum": ["choice", "count", "text"] },
          "value": {
            "nullable": true,
            "oneOf": [{ "type": "string" }, { "type": "number" }, { "type": "boolean" }]
          }
        },
        "required": ["description"]
      },
      "ApiItineraryMarketing": {
        "type": "object",
        "description": "Always present on GET itinerary. Empty state is `materials: []` and `receiverContacts: []`.",
        "properties": {
          "materials": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiItineraryMarketingItem" }
          },
          "receiverContacts": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiContactItem" }
          }
        },
        "required": ["materials", "receiverContacts"]
      },
      "ApiItineraryMarketingWrite": {
        "type": "object",
        "description": "POST body for `itinerary.marketing`. `materials` and `receiverContacts` each replace the stored list in full when sent.",
        "properties": {
          "materials": {
            "type": "array",
            "description": "Full marketing-materials list. Omitting this key leaves materials unchanged; `[]` or `null` clears them.",
            "items": { "$ref": "#/components/schemas/ApiItineraryMarketingInput" }
          },
          "receiverContacts": {
            "type": "array",
            "description": "Full marketing delivery-contact list (Lieferkontakt). Inline `ApiContactWriteInput` or `{ contactId }` (`ApiContactRefInput`). Omitting this key leaves contacts unchanged; `[]` clears them.",
            "items": {
              "oneOf": [
                { "$ref": "#/components/schemas/ApiContactRefInput" },
                { "$ref": "#/components/schemas/ApiContactWriteInput" }
              ]
            }
          }
        }
      },
      "ApiItineraryScheduleItem": {
        "allOf": [
          { "$ref": "#/components/schemas/ApiItineraryListRowBase" },
          {
            "type": "object",
            "properties": {
              "date": { "type": "string", "format": "date", "nullable": true },
              "endDate": { "type": "string", "format": "date", "nullable": true },
              "endTime": { "type": "string", "nullable": true },
              "text": { "type": "string", "nullable": true },
              "time": { "type": "string", "nullable": true }
            },
            "required": ["date", "endDate", "endTime", "text", "time"]
          }
        ]
      },
      "ApiItineraryScheduleInput": {
        "type": "object",
        "description": "`ApiItineraryScheduleInput` — POST row for `itinerary.schedules[]`. Requires `text`. Optional `_id` keeps the stored row id. The `schedules` array replaces the stored list in full.",
        "properties": {
          "_id": {
            "type": "string",
            "description": "Optional. Keep the stored row id when replacing the list."
          },
          "date": { "type": "string", "format": "date", "nullable": true },
          "endDate": { "type": "string", "format": "date", "nullable": true },
          "endTime": { "type": "string", "nullable": true },
          "text": { "type": "string" },
          "time": { "type": "string", "nullable": true }
        },
        "required": ["text"]
      },
      "ApiItinerarySpecialItem": {
        "allOf": [
          { "$ref": "#/components/schemas/ApiItineraryListRowBase" },
          {
            "type": "object",
            "properties": {
              "description": { "type": "string", "nullable": true },
              "title": { "type": "string", "nullable": true }
            },
            "required": ["description", "title"]
          }
        ]
      },
      "ApiItinerarySpecialInput": {
        "type": "object",
        "description": "`ApiItinerarySpecialInput` — POST row for `itinerary.specials[]`. Optional `_id` keeps the stored row id.",
        "properties": {
          "_id": {
            "type": "string",
            "description": "Optional. Keep the stored row id when replacing the list."
          },
          "description": { "type": "string", "nullable": true },
          "title": { "type": "string", "nullable": true }
        }
      },
      "ApiItineraryTransportItem": {
        "allOf": [
          { "$ref": "#/components/schemas/ApiItineraryListRowBase" },
          {
            "type": "object",
            "properties": {
              "transportCompany": { "type": "string", "nullable": true },
              "transportDriverName": { "type": "string", "nullable": true },
              "transportDriverPhoneNr": { "type": "string", "nullable": true },
              "transportDropOffLocation": { "type": "string", "nullable": true },
              "transportNotes": { "type": "string", "nullable": true },
              "transportPickUpDate": { "type": "string", "format": "date", "nullable": true },
              "transportPickUpLocation": { "type": "string", "nullable": true },
              "transportPickUpTime": { "type": "string", "nullable": true },
              "transportTaxiNr": { "type": "string", "nullable": true }
            },
            "required": [
              "transportCompany",
              "transportDriverName",
              "transportDriverPhoneNr",
              "transportDropOffLocation",
              "transportNotes",
              "transportPickUpDate",
              "transportPickUpLocation",
              "transportPickUpTime",
              "transportTaxiNr"
            ]
          }
        ]
      },
      "ApiItineraryTransportInput": {
        "type": "object",
        "description": "`ApiItineraryTransportInput` — POST row for `itinerary.transports[]`. Optional `_id` keeps the stored row id.",
        "properties": {
          "_id": {
            "type": "string",
            "description": "Optional. Keep the stored row id when replacing the list."
          },
          "transportCompany": { "type": "string", "nullable": true },
          "transportDriverName": { "type": "string", "nullable": true },
          "transportDriverPhoneNr": { "type": "string", "nullable": true },
          "transportDropOffLocation": { "type": "string", "nullable": true },
          "transportNotes": { "type": "string", "nullable": true },
          "transportPickUpDate": { "type": "string", "format": "date", "nullable": true },
          "transportPickUpLocation": { "type": "string", "nullable": true },
          "transportPickUpTime": { "type": "string", "nullable": true },
          "transportTaxiNr": { "type": "string", "nullable": true }
        }
      },
      "ApiItineraryTravelItem": {
        "allOf": [
          { "$ref": "#/components/schemas/ApiItineraryListRowBase" },
          {
            "type": "object",
            "properties": {
              "travelArrivalDate": { "type": "string", "format": "date", "nullable": true },
              "travelArrivalLocation": { "type": "string", "nullable": true },
              "travelArrivalTime": { "type": "string", "nullable": true },
              "travelBookingNr": { "type": "string", "nullable": true },
              "travelDepartureDate": { "type": "string", "format": "date", "nullable": true },
              "travelDepartureLocation": { "type": "string", "nullable": true },
              "travelDepartureTime": { "type": "string", "nullable": true },
              "travelNotes": { "type": "string", "nullable": true },
              "travelNr": { "type": "string", "nullable": true },
              "travelPassengerName": { "type": "string", "nullable": true },
              "travelType": { "type": "string", "nullable": true },
              "travelWebsite": { "type": "string", "nullable": true }
            },
            "required": [
              "travelArrivalDate",
              "travelArrivalLocation",
              "travelArrivalTime",
              "travelBookingNr",
              "travelDepartureDate",
              "travelDepartureLocation",
              "travelDepartureTime",
              "travelNotes",
              "travelNr",
              "travelPassengerName",
              "travelType",
              "travelWebsite"
            ]
          }
        ]
      },
      "ApiItineraryTravelInput": {
        "type": "object",
        "description": "`ApiItineraryTravelInput` — POST row for `itinerary.travels[]`. Requires `travelType`. Optional `_id` keeps the stored row id.",
        "properties": {
          "_id": {
            "type": "string",
            "description": "Optional. Keep the stored row id when replacing the list."
          },
          "travelArrivalDate": { "type": "string", "format": "date", "nullable": true },
          "travelArrivalLocation": { "type": "string", "nullable": true },
          "travelArrivalTime": { "type": "string", "nullable": true },
          "travelBookingNr": { "type": "string", "nullable": true },
          "travelDepartureDate": { "type": "string", "format": "date", "nullable": true },
          "travelDepartureLocation": { "type": "string", "nullable": true },
          "travelDepartureTime": { "type": "string", "nullable": true },
          "travelNotes": { "type": "string", "nullable": true },
          "travelNr": { "type": "string", "nullable": true },
          "travelPassengerName": { "type": "string", "nullable": true },
          "travelType": { "type": "string" },
          "travelWebsite": { "type": "string", "nullable": true }
        },
        "required": ["travelType"]
      },
      "ApiItineraryItem": {
        "type": "object",
        "description": "**Response (GET, `extend`).** `ApiItineraryItem` on `bookings[].itinerary`. Always present on GET bookings. `extend` fills category data; unrequested slices are empty lists (`[]`) or `null`, and `has*` flags are `false`. Stored values including empty string `\"\"` are returned as-is. Nested list rows always include every property (`null` if unset). GET-only: `has*` flags and list `sequenceNr`. Every itinerary list row always includes `_id`. Writable via `ApiItineraryInput` on POST.",
        "properties": {
          "artistCareContacts": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiBookingContactItem" }
          },
          "artistCareContactsNotes": { "type": "string", "nullable": true },
          "custom": {
            "properties": {
              "teamConstellation": { "type": "string", "nullable": true }
            },
            "required": ["teamConstellation"],
            "type": "object"
          },
          "flights": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiItineraryFlightItem" }
          },
          "guestList": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiItineraryGuestListItem" }
          },
          "guestListSpotCount": { "type": "number", "nullable": true },
          "hasContacts": { "type": "boolean" },
          "hasCustom": { "type": "boolean" },
          "hasFlights": { "type": "boolean" },
          "hasGuestList": { "type": "boolean" },
          "hasHotel": { "type": "boolean" },
          "hasItinerary": { "type": "boolean" },
          "hasLineup": { "type": "boolean" },
          "hasMarketingMaterials": { "type": "boolean" },
          "hasSchedules": { "type": "boolean" },
          "hasSpecials": { "type": "boolean" },
          "hasTickets": { "type": "boolean" },
          "hasTransport": { "type": "boolean" },
          "hasTravels": { "type": "boolean" },
          "hotels": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiItineraryHotelItem" }
          },
          "lineup": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiItineraryLineupItem" }
          },
          "marketing": { "$ref": "#/components/schemas/ApiItineraryMarketing" },
          "otherContacts": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiBookingContactItem" }
          },
          "otherContactsNotes": { "type": "string", "nullable": true },
          "promoterContacts": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiBookingContactItem" }
          },
          "promoterContactsNotes": { "type": "string", "nullable": true },
          "schedules": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiItineraryScheduleItem" }
          },
          "specials": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiItinerarySpecialItem" }
          },
          "technicalContacts": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiBookingContactItem" }
          },
          "technicalContactsNotes": { "type": "string", "nullable": true },
          "tickets": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiTicketsItem" }
          },
          "transports": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiItineraryTransportItem" }
          },
          "travels": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiItineraryTravelItem" }
          }
        },
        "required": [
          "artistCareContacts",
          "artistCareContactsNotes",
          "custom",
          "flights",
          "guestList",
          "guestListSpotCount",
          "hasContacts",
          "hasCustom",
          "hasFlights",
          "hasGuestList",
          "hasHotel",
          "hasItinerary",
          "hasLineup",
          "hasMarketingMaterials",
          "hasSchedules",
          "hasSpecials",
          "hasTickets",
          "hasTransport",
          "hasTravels",
          "hotels",
          "lineup",
          "marketing",
          "otherContacts",
          "otherContactsNotes",
          "promoterContacts",
          "promoterContactsNotes",
          "schedules",
          "specials",
          "technicalContacts",
          "technicalContactsNotes",
          "tickets",
          "transports",
          "travels"
        ]
      },
      "ApiDealCommissionItem": {
        "type": "object",
        "description": "Agency or manager commission on the mapped deal. Includes `percentage` and `vat`. `amount` is the stored amount when `isFixAmount` is true, and `null` when `isFixAmount` is false. The calculated commission amount is on `deal.total`. Also includes `inclusive` and `isSubtotalCommission`. Does not include `feeType`, `label`, `hint`, `feeDescription`, or `afterCommissions`. Unset scalars are `null`.",
        "properties": {
          "amount": { "type": "number", "nullable": true, "description": "Stored amount when `isFixAmount` is true. `null` when `isFixAmount` is false. The calculated commission amount is on `deal.total`." },
          "currency": { "type": "string", "nullable": true },
          "inclusive": { "type": "boolean", "nullable": true, "description": "Commission included in the artist fee (`true`) or added on top (`false`)." },
          "isFixAmount": { "type": "boolean", "nullable": true, "description": "Fixed commission amount (`true`) or percentage (`false`)." },
          "isSubtotalCommission": { "type": "boolean", "nullable": true },
          "note": { "type": "string", "nullable": true },
          "paymentDueDate": { "type": "string", "format": "date", "nullable": true },
          "paymentMethod": { "type": "string", "nullable": true },
          "percentage": { "type": "number", "nullable": true },
          "vat": { "type": "number", "nullable": true, "description": "VAT percentage on this commission. Commission VAT is calculated into `deal.total`." }
        },
        "required": [
          "amount",
          "currency",
          "inclusive",
          "isFixAmount",
          "isSubtotalCommission",
          "note",
          "paymentDueDate",
          "paymentMethod",
          "percentage",
          "vat"
        ]
      },
      "ApiDealExpensesItem": {
        "type": "object",
        "description": "Expenses on the mapped deal. Includes `afterCommissions`. Does not include `feeType`, `label`, `hint`, `feeDescription`, `vat`, `percentage`, `inclusive`, `isFixAmount`, or `isSubtotalCommission`. Unset scalars are `null`.",
        "properties": {
          "afterCommissions": { "type": "boolean", "nullable": true, "description": "Expenses applied after commissions." },
          "amount": { "type": "number", "nullable": true },
          "currency": { "type": "string", "nullable": true },
          "note": { "type": "string", "nullable": true },
          "paymentDueDate": { "type": "string", "format": "date", "nullable": true },
          "paymentMethod": { "type": "string", "nullable": true }
        },
        "required": [
          "afterCommissions",
          "amount",
          "currency",
          "note",
          "paymentDueDate",
          "paymentMethod"
        ]
      },
      "ApiDealFeeItem": {
        "type": "object",
        "description": "Guaranteed or additional fee on the mapped deal. Does not include `feeType`, `label`, `hint`, `feeDescription`, `afterCommissions`, `vat`, `percentage`, `inclusive`, `isFixAmount`, or `isSubtotalCommission`. Unset scalars are `null`.",
        "properties": {
          "amount": { "type": "number", "nullable": true },
          "currency": { "type": "string", "nullable": true },
          "note": { "type": "string", "nullable": true },
          "paymentDueDate": { "type": "string", "format": "date", "nullable": true },
          "paymentMethod": { "type": "string", "nullable": true }
        },
        "required": [
          "amount",
          "currency",
          "note",
          "paymentDueDate",
          "paymentMethod"
        ]
      },
      "ApiDealOtherCommissionItem": {
        "type": "object",
        "description": "Other commission on the mapped deal. Same fields as an agency or manager commission, plus `feeDescription`. Does not include `feeType`, `label`, `hint`, or `afterCommissions`.",
        "properties": {
          "amount": { "type": "number", "nullable": true, "description": "Stored amount when `isFixAmount` is true. `null` when `isFixAmount` is false. The calculated commission amount is on `deal.total`." },
          "currency": { "type": "string", "nullable": true },
          "feeDescription": { "type": "string", "nullable": true, "description": "Label for the other commission. Only this commission line includes `feeDescription`." },
          "inclusive": { "type": "boolean", "nullable": true },
          "isFixAmount": { "type": "boolean", "nullable": true },
          "isSubtotalCommission": { "type": "boolean", "nullable": true },
          "note": { "type": "string", "nullable": true },
          "paymentDueDate": { "type": "string", "format": "date", "nullable": true },
          "paymentMethod": { "type": "string", "nullable": true },
          "percentage": { "type": "number", "nullable": true },
          "vat": { "type": "number", "nullable": true }
        },
        "required": [
          "amount",
          "currency",
          "feeDescription",
          "inclusive",
          "isFixAmount",
          "isSubtotalCommission",
          "note",
          "paymentDueDate",
          "paymentMethod",
          "percentage",
          "vat"
        ]
      },
      "ApiDealTotalAmount": {
        "type": "object",
        "description": "Calculated money total from `calculateTotalFee`. `label` and `hint` are omitted. `percentage` is a number, or `null` when the total has no percentage or commission VAT rates are mixed. `percentageCommissionType` is `various commissions` when commission VAT rates are mixed, and `single commission` otherwise.",
        "properties": {
          "amount": { "type": "number" },
          "currency": { "type": "string" },
          "percentage": { "type": "number", "nullable": true },
          "percentageCommissionType": {
            "type": "string",
            "enum": ["single commission", "various commissions"]
          },
          "vat": { "type": "number" }
        },
        "required": ["percentage", "percentageCommissionType"]
      },
      "ApiDealTotal": {
        "type": "object",
        "description": "Calculated deal totals (`deal.total`). Amounts, currencies, and commission percentages match the mapped deal. Commission amounts are under `commissions` keyed by fee type (`agency commission`, `manager commission`, `other commission`), each `{ amount, percentage }`.",
        "properties": {
          "artistFee": { "$ref": "#/components/schemas/ApiDealTotalAmount" },
          "commission": { "$ref": "#/components/schemas/ApiDealTotalAmount" },
          "commissionInclVAT": { "$ref": "#/components/schemas/ApiDealTotalAmount" },
          "commissionOnSubtotal": { "$ref": "#/components/schemas/ApiDealTotalAmount" },
          "commissionOnSubtotalInclVAT": { "$ref": "#/components/schemas/ApiDealTotalAmount" },
          "commissionOnSubtotalVAT": { "$ref": "#/components/schemas/ApiDealTotalAmount" },
          "commissionVAT": { "$ref": "#/components/schemas/ApiDealTotalAmount" },
          "commissions": {
            "type": "object",
            "properties": {
              "agency commission": {
                "type": "object",
                "properties": {
                  "amount": { "type": "number" },
                  "percentage": { "type": "number" }
                },
                "required": ["amount"]
              },
              "manager commission": {
                "type": "object",
                "properties": {
                  "amount": { "type": "number" },
                  "percentage": { "type": "number" }
                },
                "required": ["amount"]
              },
              "other commission": {
                "type": "object",
                "properties": {
                  "amount": { "type": "number" },
                  "percentage": { "type": "number" }
                },
                "required": ["amount"]
              }
            },
            "required": ["agency commission", "manager commission", "other commission"]
          },
          "currency": { "type": "string" },
          "expenses": { "$ref": "#/components/schemas/ApiDealTotalAmount" },
          "expensesAfterCommissions": { "$ref": "#/components/schemas/ApiDealTotalAmount" },
          "netTotalFee": { "$ref": "#/components/schemas/ApiDealTotalAmount" },
          "netTotalFeeExclVAT": { "$ref": "#/components/schemas/ApiDealTotalAmount" },
          "subtotalFee": { "$ref": "#/components/schemas/ApiDealTotalAmount" },
          "totalCommission": { "$ref": "#/components/schemas/ApiDealTotalAmount" },
          "totalFee": { "$ref": "#/components/schemas/ApiDealTotalAmount" },
          "totalFeeExclVAT": { "$ref": "#/components/schemas/ApiDealTotalAmount" }
        },
        "required": [
          "artistFee",
          "commission",
          "commissionInclVAT",
          "commissionOnSubtotal",
          "commissionOnSubtotalInclVAT",
          "commissionOnSubtotalVAT",
          "commissionVAT",
          "commissions",
          "currency",
          "expenses",
          "expensesAfterCommissions",
          "netTotalFee",
          "netTotalFeeExclVAT",
          "subtotalFee",
          "totalCommission",
          "totalFee",
          "totalFeeExclVAT"
        ]
      },
      "ApiDealItem": {
        "type": "object",
        "description": "**Response (GET).** Mapped Optune deal for one booking, including `total` from the same calculation Optune uses. Built at read time from the booking's stored deal, so it stays current after deal changes. `null` when deal data is hidden. Not writable.",
        "properties": {
          "agencyCommission": { "$ref": "#/components/schemas/ApiDealCommissionItem" },
          "artistFeeAdditional": { "$ref": "#/components/schemas/ApiDealFeeItem" },
          "artistFeeExpenses": { "$ref": "#/components/schemas/ApiDealExpensesItem" },
          "artistFeeGuaranteed": { "$ref": "#/components/schemas/ApiDealFeeItem" },
          "currency": { "type": "string", "nullable": true },
          "dealType": { "type": "string", "nullable": true, "description": "Stored Optune deal type from `booking.dealType`. `null` when unset. Passed through as stored." },
          "managerCommission": { "$ref": "#/components/schemas/ApiDealCommissionItem" },
          "otherCommission": { "$ref": "#/components/schemas/ApiDealOtherCommissionItem" },
          "total": { "$ref": "#/components/schemas/ApiDealTotal" }
        },
        "required": [
          "agencyCommission",
          "artistFeeAdditional",
          "artistFeeExpenses",
          "artistFeeGuaranteed",
          "currency",
          "dealType",
          "managerCommission",
          "otherCommission",
          "total"
        ]
      },
      "ApiBookingItem": {
        "type": "object",
        "description": "**Response (GET).** Lineup booking on `ApiEventItem`. Every booking field is always present: stored values (including empty string `\"\"`) as-is, empty lists as `[]`, missing scalars as `null`. GET-only extras vs `ApiBookingWriteInput`: `_id`, `artistName`, `artistSlug`, `deal`, `fee`, `feeCurrency`, `managerFee`, `managerFeeCurrency`, `playStartDate`, `playEndDate`, `contacts[]`, and GET-shaped `itinerary`. `fee` and `feeCurrency` come from `deal.total.artistFee`. `managerFee` and `managerFeeCurrency` come from `deal.total.totalCommission`. The API key needs `read:deal`, and artist or agency deal rights must allow it. When hidden, `deal`, `fee`, `feeCurrency`, `managerFee`, and `managerFeeCurrency` are `null`.",
        "properties": {
          "_id": { "type": "string" },
          "artistId": { "type": "string" },
          "artistName": { "type": "string" },
          "artistSlug": { "type": "string" },
          "contacts": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiBookingContactItem" }
          },
          "deal": {
            "anyOf": [{ "$ref": "#/components/schemas/ApiDealItem" }, { "type": "null" }],
            "description": "Full mapped deal for this booking, including commissions and `total`. `fee` and `managerFee` are derived from `total`. `null` when the caller cannot see deal data."
          },
          "fee": {
            "type": "number",
            "nullable": true,
            "description": "Read-only artist fee, `deal.total.artistFee.amount` from `calculateTotalFee`. Not the stored booking `fee`. `null` when the deal is hidden or not loaded."
          },
          "feeCurrency": {
            "type": "string",
            "nullable": true,
            "description": "Read-only currency of `fee`, `deal.total.artistFee.currency` from `calculateTotalFee`. Not the stored booking `feeCurrency`. `null` when the deal is hidden or not loaded."
          },
          "internalAgencyComment": {
            "type": "string",
            "nullable": true,
            "description": "Internal agency note. `null` unless the key has `read:internalComment`. `read:deal` and `write:internalComment` do not grant read access."
          },
          "internalComment": {
            "type": "string",
            "nullable": true,
            "description": "Booking internal comment. Not gated by `read:internalComment`."
          },
          "isPublished": { "type": "boolean" },
          "itinerary": { "$ref": "#/components/schemas/ApiItineraryItem" },
          "managerFee": {
            "type": "number",
            "nullable": true,
            "description": "Read-only total commission, `deal.total.totalCommission.amount` from `calculateTotalFee`. Not the stored booking `managerFee`. `null` when the deal is hidden or not loaded."
          },
          "managerFeeCurrency": {
            "type": "string",
            "nullable": true,
            "description": "Read-only currency of `managerFee`, `deal.total.totalCommission.currency` from `calculateTotalFee`. Not the stored booking `managerFeeCurrency`. `null` when the deal is hidden or not loaded."
          },
          "operationalPeriod": {
            "anyOf": [
              { "$ref": "#/components/schemas/ApiBookingOperationalPeriod" },
              { "type": "null" }
            ]
          },
          "playDuration": { "type": "string", "nullable": true },
          "playEndDate": { "type": "string", "format": "date", "nullable": true },
          "playEndTime": { "type": "string", "nullable": true },
          "playStartDate": { "type": "string", "format": "date", "nullable": true },
          "playStartTime": { "type": "string", "nullable": true },
          "status": {
            "type": "string",
            "description": "Booking status. One of `idea`, `pending`, `confirmed`, `declined`, `canceled`.",
            "enum": ["idea", "pending", "confirmed", "declined", "canceled"]
          },
          "statusCommentCanceled": { "type": "string", "nullable": true }
        },
        "required": [
          "_id",
          "artistId",
          "artistName",
          "artistSlug",
          "contacts",
          "deal",
          "fee",
          "feeCurrency",
          "internalAgencyComment",
          "internalComment",
          "isPublished",
          "itinerary",
          "managerFee",
          "managerFeeCurrency",
          "operationalPeriod",
          "playDuration",
          "playEndDate",
          "playEndTime",
          "playStartDate",
          "playStartTime",
          "status",
          "statusCommentCanceled"
        ]
      },
      "ApiTicketsItem": {
        "type": "object",
        "description": "`ApiTicketsItem` — ticket sales row on `bookings[].itinerary.tickets` (GET with `extend=tickets`). Writable via `ApiItineraryTicketsInput` on POST. Also on deprecated event-root `tickets`. Every field is always present. Uncaptured `sales`/`price` are `null`, never `0`. Includes `_id`.",
        "properties": {
          "_id": {
            "type": "string",
            "nullable": true,
            "description": "Stored ticket id. Always present on GET. Send on POST to keep this stored id when replacing the list."
          },
          "currency": { "type": "string", "nullable": true },
          "description": { "type": "string", "nullable": true },
          "isSoldOut": { "type": "boolean" },
          "link": {
            "type": "string",
            "nullable": true,
            "description": "Presale URL. Returned for every ticket status, including `comingSoon` and `soldOut`, when a link is stored. `null` only when no link is stored."
          },
          "notes": { "type": "string", "nullable": true },
          "price": { "type": "number", "nullable": true },
          "priceCurrency": { "type": "string", "nullable": true },
          "sales": {
            "type": "number",
            "nullable": true,
            "description": "Sold count. `null` means uncaptured, not zero sold."
          },
          "sequenceNr": {
            "readOnly": true,
            "type": "number",
            "nullable": true,
            "description": "Assigned from array order on write."
          },
          "status": {
            "type": "string",
            "nullable": true,
            "description": "Ticket sale status. Values: `comingSoon`, `lastTickets`, `remainingTickets`, `soldOut`, `tickets`."
          }
        },
        "required": [
          "_id",
          "currency",
          "description",
          "isSoldOut",
          "link",
          "notes",
          "price",
          "priceCurrency",
          "sales",
          "sequenceNr",
          "status"
        ]
      },
      "ApiItineraryTicketsInput": {
        "type": "object",
        "description": "`ApiItineraryTicketsInput` — POST row for `bookings[0].itinerary.tickets`. Sending `tickets` replaces the stored list in full. Optional `_id` keeps the stored row id; omit `_id` to create a new id. `sequenceNr` is assigned from array order.",
        "properties": {
          "_id": {
            "type": "string",
            "description": "Optional. Keep this stored ticket id when replacing the list."
          },
          "currency": { "type": "string", "nullable": true },
          "description": { "type": "string", "nullable": true },
          "isSoldOut": { "type": "boolean" },
          "link": { "type": "string", "nullable": true },
          "notes": { "type": "string", "nullable": true },
          "price": { "type": "number", "nullable": true },
          "priceCurrency": { "type": "string", "nullable": true },
          "sales": { "type": "number", "nullable": true },
          "soldOut": { "type": "boolean" },
          "status": {
            "type": "string",
            "nullable": true,
            "description": "Ticket sale status. Values: `comingSoon`, `lastTickets`, `remainingTickets`, `soldOut`, `tickets`. A stored `link` is kept on write and returned on GET for every status."
          }
        }
      },
      "ApiEventItem": {
        "type": "object",
        "description": "**Response (GET).** Mapped event from list/detail. Differs from `ApiEventWriteInput` / `ApiEventUpdateInput` — GET-only: `_id`, `slug`, `version`, `eventStatus`, `type`, `artwork`, `attachments`, status flags, nested `venue` (write `venueId`). `eventType` is writable. `fee` and `managerFee` are read-only sums of billable booking amounts from `calculateTotalFee` (declined and canceled bookings excluded). `feeCurrency` is the shared `artistFee.currency` of those bookings, and `managerFeeCurrency` is the shared `totalCommission.currency`. A mixed or missing currency is `null`. They are not the stored event fee or its currency. Every field is always present: stored values (including empty string `\"\"`) as-is, empty lists as `[]`, missing scalars as `null`. Calendar dates use `YYYY-MM-DD`. `bookingCapacity` is the show capacity (writable). Event-root `tickets` is **deprecated** — use `bookings[].itinerary.tickets` with `extend=tickets` instead. Uncaptured `sales`/`bookingCapacity` are `null`.",
        "properties": {
          "_id": { "type": "string" },
          "artwork": {
            "anyOf": [{ "$ref": "#/components/schemas/ApiAttachmentItem" }, { "type": "null" }]
          },
          "attachments": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiAttachmentItem" }
          },
          "bookingCapacity": {
            "type": "string",
            "nullable": true,
            "description": "Show capacity for this booking. Separate from `venue.capacity`. Unknown is `null`, never `0`."
          },
          "bookings": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiBookingItem" }
          },
          "description": { "type": "string", "nullable": true },
          "doors": { "type": "string", "nullable": true },
          "endDate": { "type": "string", "format": "date", "nullable": true },
          "eventStatus": { "type": "string", "description": "Event status enum." },
          "eventType": { "type": "string", "nullable": true, "description": "Stored Optune event type label, such as Festival. Writable free-form string, distinct from `type` (the event document type, such as Simple Booking or Manager Booking). Always present. `null` when unset. `null` on write clears it. Omit on update to leave it unchanged." },
          "fee": {
            "type": "number",
            "nullable": true,
            "description": "Read-only sum of billable booking artist fees (`calculateTotalFee` `artistFee.amount`). `null` when none are visible."
          },
          "feeCurrency": {
            "type": "string",
            "nullable": true,
            "description": "Read-only shared `artistFee.currency` of the billable bookings in `fee`. `null` when none are visible or the currencies differ."
          },
          "internalComment": {
            "type": "string",
            "nullable": true,
            "description": "Internal event note. Not gated by `read:internalComment` or `write:internalComment`."
          },
          "isCanceled": { "type": "boolean" },
          "isClosed": { "type": "boolean" },
          "isDeleted": { "type": "boolean" },
          "isOk": { "type": "boolean" },
          "managerFee": {
            "type": "number",
            "nullable": true,
            "description": "Read-only sum of billable booking total commissions (`calculateTotalFee` `totalCommission.amount`). `null` when none are visible."
          },
          "managerFeeCurrency": {
            "type": "string",
            "nullable": true,
            "description": "Read-only shared `totalCommission.currency` of the billable bookings in `managerFee`. `null` when none are visible or the currencies differ."
          },
          "slug": { "type": "string" },
          "startDate": { "type": "string", "format": "date" },
          "tickets": {
            "deprecated": true,
            "type": "array",
            "description": "**Deprecated.** Legacy event-level ticket rows. Use `bookings[].itinerary.tickets` (request with `extend=tickets`). Writable only via `bookings[0].itinerary.tickets` on POST — not on the event root.",
            "items": { "$ref": "#/components/schemas/ApiTicketsItem" }
          },
          "title": { "type": "string", "nullable": true },
          "type": { "type": "string", "description": "Event document type, such as Simple Booking or Manager Booking. Not the writable `eventType` label." },
          "updatedAt": { "type": "string", "format": "date-time" },
          "venue": {
            "anyOf": [{ "$ref": "#/components/schemas/ApiVenueItem" }, { "type": "null" }]
          },
          "version": { "type": "number" },
          "website": { "type": "string", "nullable": true }
        },
        "required": [
          "_id",
          "artwork",
          "attachments",
          "bookingCapacity",
          "bookings",
          "description",
          "doors",
          "endDate",
          "eventStatus",
          "eventType",
          "fee",
          "feeCurrency",
          "internalComment",
          "isCanceled",
          "isClosed",
          "isDeleted",
          "isOk",
          "managerFee",
          "managerFeeCurrency",
          "slug",
          "startDate",
          "tickets",
          "title",
          "type",
          "updatedAt",
          "venue",
          "version",
          "website"
        ]
      },
      "ProtectedArtistsListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiArtistItem" }
          },
          "meta": { "$ref": "#/components/schemas/PaginationMeta" }
        },
        "required": ["data", "meta"]
      },
      "ProtectedArtistSingleResponse": {
        "type": "object",
        "properties": {
          "data": { "$ref": "#/components/schemas/ApiArtistItem" }
        },
        "required": ["data"]
      },
      "ProtectedContactsListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiContactItem" }
          },
          "meta": { "$ref": "#/components/schemas/PaginationMeta" }
        },
        "required": ["data", "meta"]
      },
      "ProtectedContactSingleResponse": {
        "type": "object",
        "properties": {
          "data": { "$ref": "#/components/schemas/ApiContactItem" }
        },
        "required": ["data"]
      },
      "ProtectedVenuesListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiVenueItem" }
          },
          "meta": { "$ref": "#/components/schemas/PaginationMeta" }
        },
        "required": ["data", "meta"]
      },
      "ProtectedVenueSingleResponse": {
        "type": "object",
        "properties": {
          "data": { "$ref": "#/components/schemas/ApiVenueItem" }
        },
        "required": ["data"]
      },
      "ProtectedEventsListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiEventItem" }
          },
          "meta": { "$ref": "#/components/schemas/PaginationMeta" }
        },
        "required": ["data", "meta"]
      },
      "ProtectedEventSingleResponse": {
        "type": "object",
        "properties": {
          "data": { "$ref": "#/components/schemas/ApiEventItem" }
        },
        "required": ["data"]
      },
      "ProtectedWriteResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": { "type": "string", "nullable": true },
              "slug": { "type": "string", "nullable": true }
            },
            "required": ["id", "slug"]
          }
        },
        "required": ["data"]
      },
      "ProtectedDeleteResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "success": { "type": "boolean" }
            },
            "required": ["success"]
          }
        },
        "required": ["data"]
      },
      "ApiContactWriteInput": {
        "type": "object",
        "description": "**Request (POST).** `ApiContactInput` / `ApiContactWriteInput`. Create requires `name` or `company` (at least one). Not the same as `ApiContactItem`. `phoneNr` → stored mobile; `phoneNrOther` → stored landline (`otherPhoneNr` renamed). Do not send `mobilePhoneNr`. `billingAddress` sets the separate billing address. On update, omit it to leave the stored address unchanged; `null` removes it; `null` on a nested field clears that field only. Omit `website` and `otherLink` to leave the stored website and additional links unchanged, including links that are not those two fields. Updates on soft-deleted contacts return 404.",
        "properties": {
          "addressLine1": { "type": "string", "nullable": true },
          "addressLine2": { "type": "string", "nullable": true },
          "billingAddress": {
            "anyOf": [
              { "$ref": "#/components/schemas/ApiBillingAddressWrite" },
              { "type": "null" }
            ],
            "description": "Separate billing address. `null` removes it. Omit the key on update to leave it unchanged."
          },
          "city": { "type": "string", "nullable": true },
          "company": { "type": "string", "nullable": true },
          "contactNr": { "type": "string", "nullable": true },
          "contactType": { "type": "string", "nullable": true },
          "country": { "type": "string", "nullable": true },
          "email": { "type": "string", "nullable": true },
          "language": { "type": "string", "nullable": true },
          "name": { "type": "string", "nullable": true },
          "notes": { "type": "string", "nullable": true },
          "otherLink": { "type": "string", "nullable": true },
          "phoneNr": {
            "type": "string",
            "nullable": true,
            "description": "Primary phone (stored as `mobilePhoneNr`)."
          },
          "phoneNrOther": {
            "type": "string",
            "nullable": true,
            "description": "Secondary phone (stored as `phoneNr`)."
          },
          "position": { "type": "string", "nullable": true },
          "postalCode": { "type": "string", "nullable": true },
          "state": { "type": "string", "nullable": true },
          "vatNr": { "type": "string", "nullable": true },
          "website": {
            "type": "string",
            "nullable": true,
            "description": "Website URL. Omit on update to leave the stored website and any other stored links unchanged."
          }
        }
      },
      "ApiVenueWriteInput": {
        "type": "object",
        "description": "**Request (POST).** Venue create body (`ApiVenueInput`). `city`, `country`, and `name` are required strings on create. Same `capacity` integer (≥ 0) as on GET (`ApiVenueItem`). Optional `venueType` is a string array of free-form labels (same shape on GET). The created venue is always private. This API does not set public visibility or verified status.",
        "properties": {
          "capacity": { "minimum": 0, "type": "integer", "nullable": true },
          "city": { "type": "string" },
          "country": { "type": "string" },
          "description": { "type": "string", "nullable": true },
          "email": { "type": "string", "nullable": true },
          "name": { "type": "string" },
          "phoneNr": { "type": "string", "nullable": true },
          "postalCode": { "type": "string", "nullable": true },
          "streetName": { "type": "string", "nullable": true },
          "streetNumber": { "type": "string", "nullable": true },
          "venueType": {
            "type": "array",
            "items": { "type": "string" },
            "nullable": true,
            "description": "Free-form venue type labels (e.g. ClubHall, OpenAirStage)."
          },
          "website": { "type": "string", "nullable": true }
        },
        "required": ["city", "country", "name"]
      },
      "ApiVenueUpdateInput": {
        "type": "object",
        "description": "**Request (POST update).** Partial venue update. `city`, `country`, and `name` are optional. `capacity` is an integer ≥ 0 when sent. Does not change `visibility`, `isPublic`, or `isVerified`. The group must be owner or manager on the venue. A verified public venue owned by another group cannot be updated here.",
        "properties": {
          "capacity": { "minimum": 0, "type": "integer", "nullable": true },
          "city": { "type": "string" },
          "country": { "type": "string" },
          "description": { "type": "string", "nullable": true },
          "email": { "type": "string", "nullable": true },
          "name": { "type": "string" },
          "phoneNr": { "type": "string", "nullable": true },
          "postalCode": { "type": "string", "nullable": true },
          "streetName": { "type": "string", "nullable": true },
          "streetNumber": { "type": "string", "nullable": true },
          "venueType": {
            "type": "array",
            "items": { "type": "string" },
            "nullable": true,
            "description": "Free-form venue type labels (e.g. ClubHall, OpenAirStage)."
          },
          "website": { "type": "string", "nullable": true }
        }
      },
      "ApiItineraryInput": {
        "type": "object",
        "description": "**Request (POST).** `ApiItineraryInput` on `bookings[0].itinerary`. Not `ApiItineraryItem` (no `has*` or `sequenceNr`). Contact lists: `ApiContactInput` or `ApiContactRefInput`. Every itinerary list row accepts optional `_id` to keep the stored id. List fields (including tickets) replace the stored list as-is. Do not send `sequenceNr`.",
        "properties": {
          "artistCareContacts": {
            "type": "array",
            "description": "Inline `ApiContactInput` or `{ contactId }` (`ApiContactRefInput`).",
            "items": {
              "oneOf": [
                { "$ref": "#/components/schemas/ApiContactRefInput" },
                { "$ref": "#/components/schemas/ApiContactWriteInput" }
              ]
            }
          },
          "artistCareContactsNotes": { "type": "string", "nullable": true },
          "custom": {
            "nullable": true,
            "properties": {
              "teamConstellation": { "type": "string", "nullable": true }
            },
            "type": "object"
          },
          "flights": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiItineraryFlightInput" }
          },
          "guestList": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiItineraryGuestListInput" }
          },
          "guestListSpotCount": { "type": "number", "nullable": true },
          "hotels": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiItineraryHotelInput" }
          },
          "lineup": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiItineraryLineupInput" }
          },
          "marketing": { "$ref": "#/components/schemas/ApiItineraryMarketingWrite" },
          "otherContacts": {
            "type": "array",
            "items": {
              "oneOf": [
                { "$ref": "#/components/schemas/ApiContactRefInput" },
                { "$ref": "#/components/schemas/ApiContactWriteInput" }
              ]
            }
          },
          "otherContactsNotes": { "type": "string", "nullable": true },
          "promoterContacts": {
            "type": "array",
            "items": {
              "oneOf": [
                { "$ref": "#/components/schemas/ApiContactRefInput" },
                { "$ref": "#/components/schemas/ApiContactWriteInput" }
              ]
            }
          },
          "promoterContactsNotes": { "type": "string", "nullable": true },
          "schedules": {
            "type": "array",
            "description": "Full schedule list. Replaces the stored list as-is.",
            "items": { "$ref": "#/components/schemas/ApiItineraryScheduleInput" }
          },
          "specials": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiItinerarySpecialInput" }
          },
          "technicalContacts": {
            "type": "array",
            "items": {
              "oneOf": [
                { "$ref": "#/components/schemas/ApiContactRefInput" },
                { "$ref": "#/components/schemas/ApiContactWriteInput" }
              ]
            }
          },
          "technicalContactsNotes": { "type": "string", "nullable": true },
          "tickets": {
            "type": "array",
            "description": "Ticket-sales list. Sending this field replaces the stored list in full. Optional `_id` on rows keeps the stored ticket id. `[]` or `null` clears all categories.",
            "items": { "$ref": "#/components/schemas/ApiItineraryTicketsInput" }
          },
          "transports": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiItineraryTransportInput" }
          },
          "travels": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ApiItineraryTravelInput" }
          }
        }
      },
      "ApiItineraryWriteInput": {
        "$ref": "#/components/schemas/ApiItineraryInput"
      },
      "ApiEventWriteInput": {
        "type": "object",
        "description": "**Request (POST create).** Event create body (`ApiEventInput`). Not `ApiEventItem`. Requires `title`, `startDate` (a real `YYYY-MM-DD` day), and exactly one booking with `artistId`. Optional: `bookingCapacity`, `description`, `doors` (`HH:mm`), `endDate`, `eventType` (free-form label, distinct from GET `type`), `internalComment`, `venueId`, `website`. `2026-02-30` returns 400 and does not create a booking. `bookings[0].internalAgencyComment` requires scope `write:internalComment`; without it the request is rejected. Do not send `fee`, `feeCurrency`, `managerFee`, or `managerFeeCurrency`. Ticket sales: `bookings[0].itinerary.tickets` only. `endDate` ≥ `startDate` on create and update. `venueId` may be a non-deleted venue the group belongs to, including a private venue. A public venue owned by another group is accepted only when it is active and Optune-verified (`verifiedAt` set). Use `venueId`, not nested `venue`. `bookingCapacity` is the show capacity, separate from venue capacity.",
        "properties": {
          "bookingCapacity": {
            "type": "string",
            "nullable": true,
            "description": "Show capacity for this booking. Separate from `venue.capacity`. Unknown is `null`, never `0`."
          },
          "bookings": {
            "type": "array",
            "minItems": 1,
            "maxItems": 1,
            "items": { "$ref": "#/components/schemas/ApiBookingWriteInput" }
          },
          "description": { "type": "string", "nullable": true },
          "doors": { "type": "string", "nullable": true, "description": "Time label (HH:mm)." },
          "endDate": { "type": "string", "format": "date", "nullable": true, "description": "Real calendar date (`YYYY-MM-DD`), on or after `startDate`. `2026-02-30` is rejected." },
          "eventType": { "type": "string", "nullable": true, "description": "Stored Optune event type label. Omit it to leave the type unset on create." },
          "internalComment": { "type": "string", "nullable": true, "description": "Internal event note. Not gated by `write:internalComment`." },
          "startDate": { "type": "string", "format": "date", "description": "Required real calendar date (`YYYY-MM-DD`). `2026-02-30` returns 400 and does not create a booking." },
          "title": { "type": "string" },
          "venueId": { "type": "string", "nullable": true },
          "website": { "type": "string", "nullable": true }
        },
        "required": ["bookings", "startDate", "title"]
      },
      "ApiEventUpdateInput": {
        "type": "object",
        "description": "**Request (POST update).** Event update body. Partial update: only fields present in the JSON are changed; `null` clears optional values; omitted fields are left unchanged. `bookings` must be an array of exactly one object (use `{}` for a no-op booking block). `title` and `startDate` are optional on update. Dates must be real calendar days; `2026-02-30` returns 400 and leaves the booking unchanged, including when the same body also sends valid fields. `endDate` must be ≥ effective `startDate` (returns 400). `eventType` is the stored label; omit it to leave it unchanged; `null` clears it. `bookings[0].internalAgencyComment` requires `write:internalComment`. A request that includes it without that scope returns 400 and does not change the event. Boolean fields such as `isPublished` cannot be set to `null`. Updates on soft-deleted events return 404. `venueId` must reference a non-deleted venue the group belongs to, including a private venue, or an active public Optune-verified venue (`verifiedAt` set) owned by another group. GET itinerary list rows always include `_id`. `bookings[0].itinerary.tickets` replaces the stored list in full; optional row `_id` keeps the stored id. `tickets: []` or `tickets: null` clears all ticket categories. Other itinerary lists also replace in full. `bookingCapacity` is the show capacity, separate from venue capacity.",
        "properties": {
          "bookingCapacity": {
            "type": "string",
            "nullable": true,
            "description": "Show capacity for this booking. Separate from `venue.capacity`. Unknown is `null`, never `0`."
          },
          "bookings": {
            "type": "array",
            "minItems": 1,
            "maxItems": 1,
            "items": { "$ref": "#/components/schemas/ApiBookingWriteInput" }
          },
          "description": { "type": "string", "nullable": true },
          "doors": { "type": "string", "nullable": true },
          "endDate": { "type": "string", "format": "date", "nullable": true, "description": "Real calendar date (`YYYY-MM-DD`). An impossible day does not change the booking." },
          "eventType": { "type": "string", "nullable": true, "description": "Stored Optune event type label. `null` clears it. Omit to leave it unchanged." },
          "internalComment": { "type": "string", "nullable": true, "description": "Internal event note. Not gated by `write:internalComment`." },
          "startDate": { "type": "string", "format": "date", "description": "Real calendar date (`YYYY-MM-DD`). `2026-02-30` returns 400 and leaves the booking unchanged." },
          "title": { "type": "string" },
          "venueId": { "type": "string", "nullable": true },
          "website": { "type": "string", "nullable": true }
        },
        "required": ["bookings"]
      },
      "ApiBookingWriteInput": {
        "type": "object",
        "description": "**Request (POST).** `ApiBookingInput` in `bookings[0]`. Create requires `artistId`. Not `ApiBookingItem` — omit GET-only `artistName`, `artistSlug`, `deal`, `playStartDate`, `playEndDate`, `contacts`. Do not send `fee`, `feeCurrency`, `managerFee`, or `managerFeeCurrency`; the deal object replaces them. `operationalPeriod` uses `ApiBookingOperationalPeriodInput`.",
        "properties": {
          "artistId": { "type": "string", "description": "Required on create only." },
          "internalAgencyComment": {
            "type": "string",
            "nullable": true,
            "description": "Internal agency note. Requires scope `write:internalComment`. Without it the whole request returns 400 and the event stays unchanged. `null` or `\"\"` clears the note when the key may write it. Omit to leave it unchanged."
          },
          "internalComment": {
            "type": "string",
            "nullable": true,
            "description": "Booking internal comment. Not gated by `write:internalComment`."
          },
          "isPublished": { "type": "boolean" },
          "itinerary": { "$ref": "#/components/schemas/ApiItineraryInput" },
          "operationalPeriod": { "$ref": "#/components/schemas/ApiBookingOperationalPeriod" },
          "playDuration": { "type": "string", "nullable": true },
          "playEndTime": { "type": "string", "nullable": true, "description": "Time `HH:mm`." },
          "playStartTime": { "type": "string", "nullable": true, "description": "Time `HH:mm`." },
          "status": {
            "type": "string",
            "description": "Booking status. Writable values are `idea`, `pending`, `confirmed`, `declined`, and `canceled`.",
            "enum": ["idea", "pending", "confirmed", "declined", "canceled"]
          },
          "statusCommentCanceled": {
            "type": "string",
            "nullable": true,
            "description": "Cancellation reason or comment; writable on create and update."
          }
        }
      }
    }
  },
  "paths": {
    "/v1/group/artists": {
      "get": {
        "tags": ["Group Protected API"],
        "operationId": "getGroupArtists",
        "summary": "List artists for API key group",
        "description": "Requires scope `read:artists`. `updatedSince` returns entries where `createdAt` or `updatedAt` is greater than or equal to the provided timestamp.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": { "type": "integer", "minimum": 1, "maximum": 200 }
          },
          { "in": "query", "name": "offset", "schema": { "type": "integer", "minimum": 0 } },
          { "in": "query", "name": "name", "schema": { "type": "string" } },
          {
            "in": "query",
            "name": "updatedSince",
            "schema": { "type": "string", "format": "date-time" }
          }
        ],
        "responses": {
          "200": {
            "description": "Artist list",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProtectedArtistsListResponse" }
              }
            }
          },
          "400": {
            "description": "Invalid query",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "429": {
            "description": "Rate-limited",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      }
    },
    "/v1/group/artists/{id}": {
      "get": {
        "tags": ["Group Protected API"],
        "operationId": "getGroupArtistById",
        "summary": "Get artist by id",
        "description": "Requires scope `read:artists`.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Artist details",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProtectedArtistSingleResponse" }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "404": {
            "description": "Artist not found",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      }
    },
    "/v1/group/events": {
      "get": {
        "tags": ["Group Protected API"],
        "operationId": "getGroupEvents",
        "summary": "List events for API key group",
        "description": "Requires scope `read:events`. Returns mapped `ApiEventItem` payloads. Optional `artist` filters by **artist slug** (URL slug, not MongoDB id) within API key scope. Without `startDate`, only future events from today onward are returned; with `startDate`, past events in range are included. `endDate` is only valid with `startDate`. `eventId` filters to a single event id. Canceled bookings and events are included by default (`includeCanceled=false`/`0`/`no` excludes them). Soft-deleted events are excluded by default; pass `includeDeleted=true`/`1`/`yes` to include them. `updatedSince` without `startDate` includes past events changed since that timestamp. `extend` may include `tickets` and `marketing` for ticket sales and Werbematerialien.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          {
            "in": "query",
            "name": "artist",
            "schema": { "type": "string" },
            "description": "Optional artist **slug** (e.g. `my-artist`), not the artist `_id`. Only events for that artist within API key scope."
          },
          {
            "in": "query",
            "name": "startDate",
            "schema": { "type": "string", "format": "date" },
            "description": "Optional start boundary (ISO date). When omitted, response is future events from today."
          },
          {
            "in": "query",
            "name": "endDate",
            "schema": { "type": "string", "format": "date" },
            "description": "Optional end boundary. Only valid when `startDate` is set."
          },
          {
            "in": "query",
            "name": "eventId",
            "schema": { "type": "string" },
            "description": "Optional filter to a single event id."
          },
          {
            "in": "query",
            "name": "updatedSince",
            "schema": { "type": "string", "format": "date-time" },
            "description": "Optional lower bound on created/updated time of source events. Without `startDate`, past events that changed since this timestamp are included."
          },
          {
            "in": "query",
            "name": "includeCanceled",
            "schema": { "type": "string" },
            "description": "Default: canceled bookings and canceled events are included. Use `false`, `0`, or `no` for legacy behavior (active bookings only; event status `canceled` excluded from the query)."
          },
          {
            "in": "query",
            "name": "includeDeleted",
            "schema": { "type": "string" },
            "description": "Default: `false` (soft-deleted events are omitted). Use `true`, `1`, or `yes` to include them (`isDeleted: true`). GET by id always includes a soft-deleted event."
          },
          {
            "in": "query",
            "name": "extend",
            "schema": { "type": "string" },
            "description": "Optional itinerary categories to include on each `bookings[]` row (`bookings[].itinerary`). Comma-separated and/or repeated. Case-insensitive; `itinerary.` prefix is stripped. Values: `contacts`, `custom`, `flights`, `guestList`, `hotels`, `lineup`, `marketing`, `schedules`, `specials`, `tickets`, `transports`, `travels`. Example: `extend=contacts,lineup,tickets,marketing`."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": { "type": "integer", "minimum": 1, "maximum": 200 }
          },
          { "in": "query", "name": "offset", "schema": { "type": "integer", "minimum": 0 } }
        ],
        "responses": {
          "200": {
            "description": "Event list (`data` items are `ApiEventItem`).",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProtectedEventsListResponse" }
              }
            }
          },
          "400": {
            "description": "Invalid query",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "429": {
            "description": "Rate-limited",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      },
      "post": {
        "tags": ["Group Protected API"],
        "operationId": "createGroupEvent",
        "summary": "Create event",
        "description": "Requires scope `write:events`. Creates a booking event for the API key group (artist simple booking or agency manager booking). Returns `201` with `data.id` and `data.slug`.",
        "security": [{ "bearerAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ApiEventWriteInput" },
              "examples": {
                "createWithTicketsAndCapacity": {
                  "summary": "Create event with ticket category and show capacity",
                  "value": {
                    "title": "Openair",
                    "startDate": "2026-09-01",
                    "bookingCapacity": "Seated 120",
                    "bookings": [
                      {
                        "artistId": "artistId",
                        "itinerary": {
                          "tickets": [
                            {
                              "description": "GA",
                              "price": 35,
                              "priceCurrency": "CHF",
                              "status": "tickets"
                            }
                          ]
                        }
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Event created",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProtectedWriteResponse" }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "429": {
            "description": "Rate-limited",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      }
    },
    "/v1/group/events/{id}": {
      "get": {
        "tags": ["Group Protected API"],
        "operationId": "getGroupEventById",
        "summary": "Get event by id",
        "description": "Requires scope `read:events`. Returns a mapped `ApiEventItem`. Includes canceled and soft-deleted events (`isCanceled` / `isDeleted`) so clients can sync removals. Supports the same `extend` query as the list endpoint for `bookings[].itinerary` (include `marketing` and `tickets` for Werbematerialien and ticket sales). Nested `venue` is complete and may set `isVerified` for Optune-verified locations used on the booking.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } },
          {
            "in": "query",
            "name": "extend",
            "schema": { "type": "string" },
            "description": "Optional itinerary categories on each booking. Same values as `GET /v1/group/events`."
          }
        ],
        "responses": {
          "200": {
            "description": "Event details",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProtectedEventSingleResponse" },
                "examples": {
                  "eventWithTicketsMarketingVenue": {
                    "summary": "Event with tickets, marketing, capacities, and verified venue",
                    "value": {
                      "data": {
                        "_id": "eventId",
                        "bookingCapacity": "Seated 120",
                        "isCanceled": false,
                        "isDeleted": false,
                        "updatedAt": "2026-08-13T10:00:00.000Z",
                        "venue": {
                          "_id": "venueId",
                          "name": "Hall",
                          "capacity": 1500,
                          "city": "Zurich",
                          "country": "CH",
                          "isVerified": true,
                          "postalCode": "8000",
                          "street": "Main 1",
                          "streetName": "Main",
                          "streetNumber": "1"
                        },
                        "bookings": [
                          {
                            "itinerary": {
                              "marketing": {
                                "materials": [
                                  {
                                    "_id": "fieldId",
                                    "description": "A3 posters",
                                    "sequenceNr": 1,
                                    "type": "count",
                                    "value": 50
                                  }
                                ],
                                "receiverContacts": [
                                  {
                                    "_id": "contactId",
                                    "addressLine1": "Lagerstrasse 1",
                                    "city": "Zurich",
                                    "company": "Promo GmbH",
                                    "country": "CH",
                                    "email": "delivery@promo.ch",
                                    "name": "Delivery Contact",
                                    "phoneNr": "+41000000001",
                                    "postalCode": "8005"
                                  }
                                ]
                              },
                              "tickets": [
                                {
                                  "_id": "ticketId",
                                  "description": "GA",
                                  "isSoldOut": false,
                                  "price": 35,
                                  "priceCurrency": "CHF",
                                  "sales": 25,
                                  "sequenceNr": 1,
                                  "status": "tickets"
                                }
                              ]
                            }
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "404": {
            "description": "Event not found",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      },
      "post": {
        "tags": ["Group Protected API"],
        "operationId": "updateGroupEvent",
        "summary": "Update event",
        "description": "Requires scope `write:events`. Partial update via POST (not PATCH). Only fields sent in the body are applied; `null` clears optional values. `bookings` must contain exactly one object. Soft-deleted events cannot be updated (returns `404`). GET itinerary list rows always include `_id`. Itinerary lists including tickets replace in full when sent; optional `_id` keeps the stored row id. `tickets: []` or `tickets: null` clears all ticket categories. Changes to tickets, marketing materials, or `bookingCapacity` update event `updatedAt`. Returns `200` with `data.id` and `data.slug`.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ApiEventUpdateInput" },
              "examples": {
                "replaceTicketListKeepId": {
                  "summary": "Replace tickets in full; keep stored id on GA",
                  "value": {
                    "bookings": [
                      {
                        "itinerary": {
                          "tickets": [
                            {
                              "_id": "ticketId",
                              "description": "GA",
                              "price": 35,
                              "priceCurrency": "CHF",
                              "sales": 40,
                              "status": "tickets"
                            },
                            {
                              "description": "VIP",
                              "price": 80,
                              "priceCurrency": "CHF",
                              "sales": 2,
                              "status": "tickets"
                            }
                          ]
                        }
                      }
                    ]
                  }
                },
                "replaceTicketList": {
                  "summary": "Replace the full ticket list (omitted rows are removed)",
                  "value": {
                    "bookings": [
                      {
                        "itinerary": {
                          "tickets": [
                            {
                              "description": "GA",
                              "price": 35,
                              "priceCurrency": "CHF",
                              "sales": 40,
                              "status": "tickets"
                            },
                            {
                              "description": "VIP",
                              "price": 80,
                              "priceCurrency": "CHF",
                              "sales": 2
                            }
                          ]
                        }
                      }
                    ]
                  }
                },
                "updateCapacityAndMarketing": {
                  "summary": "Replace marketing materials and delivery contacts",
                  "value": {
                    "bookingCapacity": "Standing 400",
                    "bookings": [
                      {
                        "itinerary": {
                          "marketing": {
                            "materials": [
                              {
                                "_id": "fieldId",
                                "description": "A3 posters",
                                "type": "count",
                                "value": 80
                              },
                              {
                                "description": "Instagram ads",
                                "type": "choice",
                                "value": true
                              }
                            ],
                            "receiverContacts": [
                              { "contactId": "contactId" }
                            ]
                          }
                        }
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Event updated",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProtectedWriteResponse" }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "404": {
            "description": "Event not found, soft-deleted, or not writable",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "429": {
            "description": "Rate-limited",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      },
      "delete": {
        "tags": ["Group Protected API"],
        "operationId": "deleteGroupEvent",
        "summary": "Delete event",
        "description": "Requires scope `write:events`. Soft-deletes the event (`eventStatus` becomes `deleted`). Returns `200` with `data.success: true`.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Event deleted",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProtectedDeleteResponse" }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "404": {
            "description": "Event not found or not writable",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "429": {
            "description": "Rate-limited",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      }
    },
    "/v1/group/contacts": {
      "get": {
        "tags": ["Group Protected API"],
        "operationId": "getGroupContacts",
        "summary": "List contacts for API key group",
        "description": "Requires scope `read:contacts`. `updatedSince` returns entries where `createdAt` or `updatedAt` is greater than or equal to the provided timestamp.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": { "type": "integer", "minimum": 1, "maximum": 200 }
          },
          { "in": "query", "name": "offset", "schema": { "type": "integer", "minimum": 0 } },
          { "in": "query", "name": "name", "schema": { "type": "string" } },
          {
            "in": "query",
            "name": "updatedSince",
            "schema": { "type": "string", "format": "date-time" }
          }
        ],
        "responses": {
          "200": {
            "description": "Contact list",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProtectedContactsListResponse" }
              }
            }
          },
          "400": {
            "description": "Invalid query",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "429": {
            "description": "Rate-limited",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      },
      "post": {
        "tags": ["Group Protected API"],
        "operationId": "createGroupContact",
        "summary": "Create contact",
        "description": "Requires scope `write:contacts` and the same contact-edit rights as the app (not read-only). Optional `billingAddress` is stored with the contact. Returns `201` with `data.id` and `data.slug`.",
        "security": [{ "bearerAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ApiContactWriteInput" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Contact created",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProtectedWriteResponse" }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "429": {
            "description": "Rate-limited",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      }
    },
    "/v1/group/contacts/{id}": {
      "get": {
        "tags": ["Group Protected API"],
        "operationId": "getGroupContactById",
        "summary": "Get contact by id",
        "description": "Requires scope `read:contacts`.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Contact details",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProtectedContactSingleResponse" }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "404": {
            "description": "Contact not found",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      },
      "post": {
        "tags": ["Group Protected API"],
        "operationId": "updateGroupContact",
        "summary": "Update contact",
        "description": "Requires scope `write:contacts` and the same contact-edit rights as the app (contacts visible and not read-only; owner or manager on the contact). Partial update via POST. `billingAddress` updates only the fields sent; `null` clears a field; `billingAddress: null` removes the address. Returns `200` with `data.id` and `data.slug`.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ApiContactWriteInput" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Contact updated",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProtectedWriteResponse" }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "404": {
            "description": "Contact not found or not writable",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "429": {
            "description": "Rate-limited",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      },
      "delete": {
        "tags": ["Group Protected API"],
        "operationId": "deleteGroupContact",
        "summary": "Delete contact",
        "description": "Requires scope `write:contacts`. Soft-deletes the contact. Returns `200` with `data.success: true`.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Contact deleted",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProtectedDeleteResponse" }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "404": {
            "description": "Contact not found or not writable",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "429": {
            "description": "Rate-limited",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      }
    },
    "/v1/group/venues": {
      "get": {
        "tags": ["Group Protected API"],
        "operationId": "getGroupVenues",
        "summary": "List venues for API key group",
        "description": "Requires scope `read:venues`. Returns the group's own (private) venues with full address, coordinates, and capacity. Does not export the Optune-verified location directory — verified venues appear only nested on events that use them. `updatedSince` returns entries where `createdAt` or `updatedAt` is greater than or equal to the provided timestamp.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": { "type": "integer", "minimum": 1, "maximum": 200 }
          },
          { "in": "query", "name": "offset", "schema": { "type": "integer", "minimum": 0 } },
          { "in": "query", "name": "name", "schema": { "type": "string" } },
          {
            "in": "query",
            "name": "updatedSince",
            "schema": { "type": "string", "format": "date-time" }
          }
        ],
        "responses": {
          "200": {
            "description": "Venue list",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProtectedVenuesListResponse" }
              }
            }
          },
          "400": {
            "description": "Invalid query",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "429": {
            "description": "Rate-limited",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      },
      "post": {
        "tags": ["Group Protected API"],
        "operationId": "createGroupVenue",
        "summary": "Create venue",
        "description": "Requires scope `write:venues`. Creates a private venue. Returns `201` with `data.id` and `data.slug`.",
        "security": [{ "bearerAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ApiVenueWriteInput" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Venue created",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProtectedWriteResponse" }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "429": {
            "description": "Rate-limited",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      }
    },
    "/v1/group/venues/{id}": {
      "get": {
        "tags": ["Group Protected API"],
        "operationId": "getGroupVenueById",
        "summary": "Get venue by id",
        "description": "Requires scope `read:venues`.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Venue details",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProtectedVenueSingleResponse" }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "404": {
            "description": "Venue not found",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      },
      "post": {
        "tags": ["Group Protected API"],
        "operationId": "updateGroupVenue",
        "summary": "Update venue",
        "description": "Requires scope `write:venues`. Partial update via POST. `city`, `country`, and `name` are optional. `capacity` must be an integer when provided. The group must be owner or manager on the venue. Returns `200` with `data.id` and `data.slug`.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ApiVenueUpdateInput" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Venue updated",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProtectedWriteResponse" }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "404": {
            "description": "Venue not found or not writable",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "429": {
            "description": "Rate-limited",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      },
      "delete": {
        "tags": ["Group Protected API"],
        "operationId": "deleteGroupVenue",
        "summary": "Delete venue",
        "description": "Requires scope `write:venues`. Soft-deletes the venue. Returns `200` with `data.success: true`.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Venue deleted",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProtectedDeleteResponse" }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "404": {
            "description": "Venue not found or not writable",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          },
          "429": {
            "description": "Rate-limited",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } }
            }
          }
        }
      }
    }
  }
}
