Optune API Documentation

Protected Optune API Version 1.7.0

Agent-friendly OpenAPI spec: /openapi/v1.json

Versioned docs URL: /docs/v1

Overview

This page describes the protected group API: artists, events, contacts, and venues. List GET responses use JSON with data and meta. Create and update write endpoints return data.id and data.slug; delete endpoints return data.success.

Request and response shapes differ. Field tables are split into input schemas (POST bodies) and output schemas (GET list/detail). GET payloads include many server-derived or read-only fields (ids, slugs, flags, denormalized names, nested venues) that you cannot set on POST. Do not copy a GET response and send it back as an update body. GET itinerary list rows always include _id. Itinerary lists (tickets, schedules, marketing materials, and similar) are replaced in full when sent — see Updating itinerary lists.

Changelog from v1.4.8 to v1.6.0

Breaking

  • GET itinerary always present. Deployed docs omitted bookings[].itinerary without extend. It is now always present: unrequested lists are [], scalars null, has* flags false. Every resource field is always included (empty string kept; missing scalars are JSON null).
  • Tickets replace in full. Sending itinerary.tickets stores that array as-is. v1.4.8 merged tickets by _id when any incoming row had an id; omitted ticket categories are now removed.
  • Venue GET omits addressLine1. Use combined street (plus streetName / streetNumber) for the address line. This key is not present on venue list/detail or nested event venue.

Added

  • extend=marketing — itinerary.marketing.materials (_id, description, type text | count | choice, value) and itinerary.marketing.receiverContacts (delivery contacts).
  • Event bookingCapacity (show capacity, separate from venue.capacity).
  • List query includeDeleted (default false). GET by id still returns soft-deleted events (isDeleted: true). POST update of a soft-deleted event returns 404.
  • Nested event venue may set isVerified.
  • Venue venueType — optional string array of free-form venue type labels on create/update; always present on GET (empty list is []).

Changed

  • Itinerary list _id is always present on GET for every row (flights, guest list, hotels, lineup, schedules, specials, tickets, transports, travels, marketing materials). POST accepts optional _id to keep the stored id. Resource ids remain on events, bookings, contacts, and venues. Contact refs still use { "contactId": "…" }.
  • Ticket sales stay on bookings[].itinerary.tickets (extend=tickets). Event-root tickets remains deprecated.
  • Writing ticket sales, marketing materials, or bookingCapacity also updates event updatedAt (the field itself already existed).
  • sequenceNr is still GET-only and assigned from array order on write.
  • Venue GET still includes attachments (same category, public_id, url shape as event attachments). logo remains the primary venue image.

Clarification

  • Itinerary lists are full-replace. Sending tickets, schedules, flights, marketing materials, and similar arrays stores that list as-is (no per-row patch). Optional _id keeps the stored row id; omit _id to create a new id. See Updating itinerary lists.

Changelog v1.7.0

Added

  • Contact billing address is writable. POST /v1/group/contacts and POST /v1/group/contacts/:id accept billingAddress (name, company, address lines, postal code, city, state, country). Omit it to leave the stored address unchanged. null on a field clears that field; null for the object, or clearing the last field, removes it. GET list and detail return the same fields (null when there is no billing address). Rights are unchanged: read:contacts / write:contacts, plus the existing contact visibility and edit rights.
  • Booking deal on GET events. GET /v1/group/events and GET /v1/group/events/:id include bookings[].deal, the mapped Optune deal for that booking (guaranteed fee, additional fee, expenses, agency / manager / other commissions with percentage and amount, currencies, stored dealType, and calculated deal.total). dealType is the stored booking value, or null when unset. Fee lines omit feeType, label, hint, percentage, inclusive, isFixAmount, and isSubtotalCommission. feeDescription is only on otherCommission. afterCommissions is only on artistFeeExpenses. Line vat is only on commissions (guaranteed, additional, and expenses fees do not store VAT; commission VAT is calculated into deal.total). Commission amount is the stored amount when isFixAmount is true, and null when it is false. Commission lines keep percentage (and inclusive, isFixAmount, isSubtotalCommission) and omit feeType, label, and hint. Calculated totals omit label and hint. Each total amount includes percentage (null when there is none) and percentageCommissionType (single commission, or various commissions when commission VAT rates are mixed). Mixed rates on total.commissionVAT and total.commissionOnSubtotalVAT are returned as percentage: null and percentageCommissionType: "various commissions". It is read from the stored deal on each request. fee and feeCurrency are unchanged. deal is not writable.

Permissions

  • deal uses the same visibility as fee and feeCurrency. The API key must include read:deal. Artist members and agency managers only receive it when their deal right is visible, including per-artist deal visibility for an agency. When any of those checks hide the deal, bookings[].deal is null (and fee / feeCurrency stay null). extend does not grant deal access.

Authentication headers

Use a Bearer API key for all protected group endpoints:

Authorization: Bearer optune_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Optional legacy fallback for selected routes (only if documented by endpoint):

Content-Type: application/json { "apiKey": "optune_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }

Scopes

Read scopes:

read:artists read:contacts read:deal read:events read:internalComment read:venues

Write scopes (POST create/update, DELETE):

write:contacts write:events write:internalComment write:venues

read:deal is required to read bookings[].deal, fee, feeCurrency, managerFee, and managerFeeCurrency. It does not reveal the internal agency note. read:internalComment is required to read bookings[].internalAgencyComment. write:internalComment is required, together with write:events, to set, change, or clear that note. write:internalComment does not grant read access.

Sync and pagination contract

List endpoints follow a consistent contract: deterministic sorting, stable IDs, optional updatedSince filtering, and limit/offset pagination with a meta object.

For artists, contacts, and venues, updatedSince includes entries where createdAt or updatedAt is greater than or equal to the given date.

For events, startDate and updatedSince are optional; endDate is only valid when startDate is provided.

{ "data": [{ "_id": "..." }], "meta": { "hasMore": false, "limit": 50, "offset": 0, "total": 1 } }

limit defaults to 50 and is capped at 200. offset must be a non-negative integer (no upper cap).

Invalid date values return 400 with a structured error body.

Event extend query (list & detail)

On GET /v1/group/events and GET /v1/group/events/:id, use extend to load extra booking itinerary slices into each bookings[].itinerary object. Pass one or more categories (comma-separated in a single parameter, or repeated query params). Values are case-insensitive; an itinerary. prefix is accepted and stripped (for example itinerary.lineup → lineup).

Categories that populate bookings[].itinerary:

contacts custom flights guestList hotels lineup marketing schedules specials tickets transports travels

Example: /v1/group/events?extend=contacts,lineup,tickets&startDate=2026-01-01

When extend includes contacts, booking-level bookings[].contacts lists all contacts; the same rows are also grouped under bookings[].itinerary in artistCareContacts, promoterContacts, technicalContacts, and otherContacts (see Output: Itinerary). Other categories only add data under bookings[].itinerary.

bookings[].itinerary is always present. extend controls which slices are populated. Unrequested lists are [], unrequested scalars are null, and has* flags are false. Core booking fields (fee, play times, operational period, status) are always present when the API key has access to the booking. bookings[].deal is included on the same responses when deal rights allow it; otherwise it is null. It is not an extend category.

GET serialization: stored values (including empty string '') are returned as-is; empty lists stay []; null or missing scalars are JSON null. Keys are not omitted.

Write operations

All write requests use Content-Type: application/json and the same Bearer API key as GET. Updates use POST on the resource path (not PATCH). Deletes use DELETE and soft-delete records (events, contacts, venues remain in the database with a deleted status).

Create — POST /v1/group/{resource} → 201:

{ "data": { "id": "…", "slug": "…" } }

Update — POST /v1/group/{resource}/:id → 200 (same response shape).

Delete — DELETE /v1/group/{resource}/:id → 200:

{ "data": { "success": true } }

Event writes

Create requires title, startDate (YYYY-MM-DD), and bookings with exactly one object containing artistId. The API maps description to the stored public description. Optional event fields include bookingCapacity, doors (HH:mm), endDate, eventType, internalComment, venueId, and website (ApiEventInput also lists optional nested venue in TypeScript types; the HTTP API accepts venueId only). Ticket sales belong under bookings[0].itinerary.tickets. Event-root tickets on GET is deprecated (see Output: Event). bookingCapacity is the show capacity and is separate from venue.capacity. Uncaptured ticket sales and capacities are null (unknown), never 0.

endDate must be on or after startDate (returns 400 otherwise). Calendar dates must be real YYYY-MM-DD days. 2026-02-30 returns 400 with startDate 2026-02-30 is not a valid calendar date (the same shape is used for endDate, the operational period, and itinerary dates). Create does not insert a booking. An update that includes an invalid date does not change the existing booking, even when the same request also sends valid fields. Soft-deleted events cannot be updated (returns 404).

eventType is the stored Optune event type label (for example Festival or Club Show). It is a free-form string, separate from GET type (the event document type, such as Simple Booking or Manager Booking). Omit eventType to leave it unset on create and unchanged on update. null clears it. GET always includes it; null means unset.

bookings[0].internalAgencyComment is the internal agency note. GET list and detail return it only when the key has read:internalComment; otherwise the field is null. read:deal does not grant that read, and write:internalComment does not grant it either. Setting, changing, or clearing the note requires write:internalComment as well as write:events. A request that includes the field without that scope returns 400 and leaves the event unchanged. Omit the field to leave the stored note unchanged. null or "" clears it when the key may write it. Event internalComment and booking internalComment are separate notes and are not gated by these scopes.

venueId may reference a non-deleted venue the API key's group belongs to, including a private venue. A public venue owned by another group (visibility public, GET isPublic: true) can be used only when it is active and Optune-verified (verifiedAt is set). A private venue owned by another group is rejected (400). A public venue that is not verified and is owned by another group is rejected the same way. Creating a venue always stores visibility: private. This API does not create public or verified venues. GET /v1/group/venues returns venues where the group is owner, manager, or viewer, and does not export the verified directory. Updating a venue requires the group to be owner or manager on that venue. GET isVerified is true only when both verifiedAt and verifiedBy are set.

Update is partial: only fields present in the JSON are changed; null clears optional values; omitted fields are unchanged. The body must include bookings as an array of exactly one object (use bookings: [{}] when only updating top-level event fields). Do not send null for required booleans such as isPublished. Itinerary lists are replaced in full when present (see Updating itinerary lists). Send bookings[0].itinerary.tickets: [] or tickets: null to clear all ticket categories. Changes to ticket sales, marketing materials, or bookingCapacity update the event updatedAt timestamp. Itinerary contact lists accept inline contact objects or { "contactId": "…" } references.

sequenceNr on itinerary lists (read-only on write): On GET, list rows under bookings[].itinerary (for example schedules, lineup, guestList, flights, hotels, travels, transports, specials, marketing.materials, tickets) include sequenceNr for display order. You cannot set or update sequenceNr via POST — it is ignored if sent. On create and update, the server assigns sequenceNr automatically from the array order (first item = 1, second = 2, and so on). To reorder rows, send the full list in the desired order. See Updating itinerary lists.

Contact & venue writes

Contact create requires name or company (at least one — not both). Venue create requires name, city, and country; capacity must be an integer ≥ 0 when set (same on GET and POST). Optional venueType is a string array of free-form labels (same on GET and POST). Contact and venue updates accept any subset of writable fields. Soft-deleted contacts and venues cannot be updated (returns 404).

Updating itinerary lists

Event updates are partial at the field level: itinerary keys you omit are left unchanged.

Every itinerary list row includes _id on GET (flights, guest list, hotels, lineup, schedules, specials, tickets, transports, travels, marketing materials). On POST, optional _id keeps that stored id.

Sending a list field stores that array as-is. Tickets, marketing materials, schedules, flights, and the other itinerary lists all replace in full. Optional _id keeps the stored row id; omit _id to create a new id. GET the current list, then POST the complete array. Rows you omit are removed.

Lists that follow the full-replace rule:

flights guestList hotels lineup marketing.materials marketing.receiverContacts schedules specials tickets transports travels

[] or null on a list field clears it. Scalar itinerary fields (notes, guestListSpotCount, custom) still update independently. Contact category lists (artistCareContacts, promoterContacts, technicalContacts, otherContacts) replace the categories you send; include every category you want to keep when replacing contacts.

Replace the full ticket list (include _id to keep a stored ticket id; omitted rows are removed):

{ "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" }] } }] }

Replace schedules (any schedule not in this array is deleted):

{ "bookings": [{ "itinerary": { "schedules": [{ "text": "Soundcheck", "date": "2026-09-01", "time": "16:00" }, { "text": "Doors", "date": "2026-09-01", "time": "19:00" }] } }] }

Input vs output schemas

The protected API uses different JSON shapes for requests (POST create/update) and responses (GET list/detail). OpenAPI lists them separately: ApiEventWriteInput / ApiEventItem, ApiContactWriteInput / ApiContactItem, and so on.

Resource Input (POST body) Output (GET data[] / data)
Artists — Output only (no write endpoints)
Events Input: Event + Itinerary Output: Event + Itinerary (extend)
Contacts Input: Contact Output: Contact
Venues Input: Venue Output: Venue

Typical output-only fields (present on GET, not accepted on POST): document ids (_id), slug, version, updatedAt, status flags (eventStatus, isDeleted, …), aggregated fees, denormalized artist names, nested venue objects (use venueId on write), itinerary has* flags and sequenceNr, event artwork / attachments, and venue logo / attachments.

Input: Event (POST create / update)

Request body for POST /v1/group/events and POST /v1/group/events/:id. Validated by ApiEventSchema / ApiEventUpdateSchema, ApiBookingSchema, and ApiItinerarySchema. This is not the same object returned by GET — see Output: Event.

Event-level fields

Field Create Update Type Description
title required optional string Event title.
startDate required optional YYYY-MM-DD First event day. Must be a real calendar date. 2026-02-30 returns 400 and does not create or change a booking.
bookings required required array (1 item) Exactly one booking object; use [{}] for top-level-only updates.
description optional optional string | null Public description (null clears on update).
doors optional optional string (HH:mm) Doors-open time; stored on the event item.
endDate optional optional YYYY-MM-DD | null Must be a real calendar date and ≥ startDate. An impossible day such as 2026-02-30 returns 400.
eventType optional optional string | null Stored Optune event type label, such as Festival. Not the GET type enum. Omit to leave it unset on create or unchanged on update. null clears it.
bookingCapacity optional optional string | null Show capacity for this booking, separate from venue.capacity. Empty / unknown is null, not 0.
internalComment optional optional string | null Internal event note. Not gated by read:internalComment / write:internalComment.
venueId optional optional string | null Existing venue id for this API key; null clears on update.
website optional optional string | null Public website URL.

Booking fields (bookings[0])

Field Create Update Type Description
artistId required — string Artist id for the lineup slot.
internalAgencyComment optional optional string | null Internal agency note. Write requires write:internalComment in addition to write:events. Without it the request returns 400 and the event stays unchanged. Read requires read:internalComment.
internalComment optional optional string | null Booking internal comment. Not gated by read:internalComment / write:internalComment.
isPublished optional optional boolean Cannot be null.
itinerary optional optional ApiItineraryInput See Input: Itinerary. null clears on update.
operationalPeriod optional optional ApiBookingOperationalPeriodInput | null startDate, startTime, endDate, endTime (YYYY-MM-DD / HH:mm).
playDuration optional optional string | null Play duration label.
playEndTime optional optional string (HH:mm) | null Play end time.
playStartTime optional optional string (HH:mm) | null Play start time.
status optional optional string One of idea, pending, confirmed, declined, or canceled.
statusCommentCanceled optional optional string | null Cancellation reason when status is canceled.

Itinerary (bookings[0].itinerary)

Optional object typed as ApiItineraryInput on POST. Field-level reference: Input: Itinerary. GET response uses ApiItineraryItem — see Output: Itinerary.

Not in POST body (GET only on events/bookings): _id, slug, version, updatedAt, type, eventStatus, artwork, attachments, fee, feeCurrency, managerFee, managerFeeCurrency, isCanceled, isClosed, isDeleted, isOk, nested venue (use venueId), booking _id, artistName, artistSlug, deal, fee, feeCurrency, playStartDate, playEndDate, contacts[], itinerary has* flags and row sequenceNr.

Input: Itinerary (ApiItineraryInput, POST)

Nested under bookings[0].itinerary on event create/update. Validated by ApiItinerarySchema. List rows use the typed inputs below; contact lists accept ApiContactInput (inline) or ApiContactRefInput ({ "contactId": "…" }). On update, each list you send replaces the stored list in full — see Updating itinerary lists.

Not writable: has* flags, sequenceNr (assigned from array order on save). Every itinerary list row accepts optional _id on POST (always returned on GET). Dates: YYYY-MM-DD; times: HH:mm where validated.

Top-level (ApiItineraryInput)

Field Type Description
artistCareContacts ApiContactInput | ApiContactRefInput[] Artist care contacts.
artistCareContactsNotes string | null Notes for artist care.
custom object | null teamConstellation (string | null).
flights ApiItineraryFlightInput[] Flight rows.
guestList ApiItineraryGuestListInput[] Requires name per row.
guestListSpotCount number | null Total guest list spots.
hotels ApiItineraryHotelInput[] Hotel rows.
lineup ApiItineraryLineupInput[] Requires name per row.
marketing ApiItineraryMarketingWrite Nested marketing object. materials: freely defined rows with optional _id, description (required), type (text | count | choice), value. receiverContacts: marketing delivery contacts (Lieferkontakt) as a list of inline ApiContactInput or { "contactId": "…" }. Both arrays are full-replace on update. sequenceNr on materials is assigned from array order.
otherContacts ApiContactInput | ApiContactRefInput[] Other contacts.
otherContactsNotes string | null Notes for other contacts.
promoterContacts ApiContactInput | ApiContactRefInput[] Promoter / client contacts.
promoterContactsNotes string | null Notes for promoter contacts.
schedules ApiItineraryScheduleInput[] Requires text per row.
specials ApiItinerarySpecialInput[] Special rows.
technicalContacts ApiContactInput | ApiContactRefInput[] Technical contacts.
technicalContactsNotes string | null Notes for technical contacts.
tickets ApiItineraryTicketsInput[] Ticket sales rows. Optional _id on update (see Updating itinerary lists).
transports ApiItineraryTransportInput[] Ground transport rows.
travels ApiItineraryTravelInput[] Requires travelType per row.

ApiItineraryTicketsInput

Optional _id, currency, description, isSoldOut, link, notes, price, priceCurrency, sales, soldOut, status. Uncaptured sales and price are null, never 0. sequenceNr is assigned from array order. Optional _id keeps the stored row id; omitted rows are removed.

ApiItineraryMarketingInput / ApiItineraryMarketingWrite

Material row: optional _id, description (required), type (text, count, choice), value (number for count, boolean for choice, string for text). sequenceNr is assigned from array order. Nested under itinerary.marketing.materials. Delivery contacts are a list at itinerary.marketing.receiverContacts (inline contact or { "contactId": "…" }). Both materials and receiverContacts replace the stored list when sent. GET returns the same list as itinerary.marketing.receiverContacts (empty list []).

List row types (optional fields unless noted)

ApiItineraryFlightInput: optional _id, arrivalAirport, arrivalTime, departureAirport, departureTime, flightDate, flightNr, notes, passengerName, reservationCode.

ApiItineraryGuestListInput: name (required), optional _id, comment, spotCount.

ApiItineraryHotelInput: optional _id, hotelName, hotelAddress, hotelBookingNr, check-in/out dates and times, hotelCheckoutLate, hotelEmail, hotelNotes, hotelPhoneNr, hotelWebsite, singleRoom, doubleRoom.

ApiItineraryLineupInput: name (required), optional _id, playStartDate, playStartTime, playEndDate, playEndTime.

ApiItineraryScheduleInput: text (required), optional _id, date, time, endDate, endTime.

ApiItinerarySpecialInput: optional _id, title, description.

ApiItineraryTransportInput: optional _id, transportCompany, transportDriverName, transportDriverPhoneNr, transportDropOffLocation, transportNotes, transportPickUpDate, transportPickUpLocation, transportPickUpTime, transportTaxiNr.

ApiItineraryTravelInput: travelType (required), optional _id, departure/arrival locations, dates, times, travelBookingNr, travelNr, travelPassengerName, travelNotes, travelWebsite.

Input: Contact (POST create / update)

Request body for POST /v1/group/contacts and POST /v1/group/contacts/:id (ApiContactInput / ApiContactSchema). Not the same as Output: Contact. Phones: phoneNr (primary/mobile), phoneNrOther (secondary/landline). otherPhoneNr was renamed to phoneNrOther.

Field Create Update Type Description
name required* optional string *Required unless company is set.
company required* optional string | null *Required unless name is set.
addressLine1 optional optional string | null Address line 1.
addressLine2 optional optional string | null Address line 2.
billingAddress optional optional object | null Separate billing address (Rechnungsadresse). Omit on update to leave it unchanged. null removes it. Fields below.
city optional optional string | null City.
contactNr optional optional string | null Contact or reference number.
country optional optional string | null Country.
email optional optional string | null Email.
language optional optional string | null Preferred language.
notes optional optional string | null Notes.
otherLink optional optional string | null Additional URL. Omit on update to leave stored additional links unchanged.
phoneNr optional optional string | null Primary / mobile phone (stored as mobilePhoneNr).
phoneNrOther optional optional string | null Secondary / landline phone (stored as phoneNr). Replaces otherPhoneNr.
position optional optional string | null Role or job title.
postalCode optional optional string | null Postal code.
state optional optional string | null State or region.
contactType optional optional string | null Contact category (stored as contactType; same name on GET).
vatNr optional optional string | null VAT number.
website optional optional string | null Website URL. Omit on update to leave the stored website unchanged. Additional links and other stored links stay when website and otherLink are omitted.

Billing address (billingAddress)

Separate billing address on create and update. Same fields are returned by GET /v1/group/contacts and GET /v1/group/contacts/:id. On update, only billing fields present in the object change. null clears that field and leaves the others. null for billingAddress itself removes the address (GET then returns null). Clearing the last stored field does the same. An empty string is ignored, the same as omitting the field. This uses the existing write:contacts scope and the same contact edit rights as other contact fields.

Field Create Update Type Description
addressLine1 optional optional string | null Billing address line 1.
addressLine2 optional optional string | null Billing address line 2.
city optional optional string | null Billing city.
company optional optional string | null Billing company.
country optional optional string | null Billing country.
name optional optional string | null Billing name.
postalCode optional optional string | null Billing postal code.
state optional optional string | null Canton, state, or region.

Not in POST body (GET only): _id, slug, version, updatedAt, createdAt, agencyId, artistId, additionalInfo, additionalNotes, isDeleted, mobilePhoneNr (deprecated on GET).

Input: Venue (POST create / update)

Request body for POST /v1/group/venues and POST /v1/group/venues/:id (ApiVenueInput / ApiVenueSchema). Not the same as Output: Venue. Create requires name, city, and country (strings). Update treats those as optional. capacity is an integer ≥ 0. The created venue is always private (visibility: private, isPublic: false). Public and verified flags are not writable.

Field Create Update Type Description
name required optional string Venue name.
city required optional string City.
country required optional string Country.
capacity optional optional integer | null Capacity as integer ≥ 0 (same type on GET).
description optional optional string | null Description.
email optional optional string | null Email.
phoneNr optional optional string | null Phone number.
postalCode optional optional string | null Postal code.
streetName optional optional string | null Street name (stored in location components).
streetNumber optional optional string | null Street number.
venueType optional optional string[] | null Free-form venue type labels (e.g. ClubHall, OpenAirStage). Same type on GET; omitted or empty clears on update when sent as [].
website optional optional string | null Website URL.

Not in POST body (GET only): _id, slug, version, updatedAt, street, locality, localityFull, logo, attachments, map, placeId, status, type, visibility, isPlace, isPublic, isVerified, isDeleted.

List Artists

GET /v1/group/artists

Scope: read:artists

Query: limit, offset, name, updatedSince

Sync filter: createdAt OR updatedAt >= updatedSince

Get Artist by ID

GET /v1/group/artists/:id

Scope: read:artists

Path params: id

List Events

GET /v1/group/events

Scope: read:events

Query: limit, offset, artist, startDate, endDate, eventId, includeCanceled, includeDeleted, updatedSince, extend

Filters: artist is optional — pass the artist slug (e.g. my-artist), not the MongoDB _id; scoped to the API key. updatedSince is optional; endDate is only valid when startDate is provided. extend selects itinerary slices on each booking (see Event extend query). By default, canceled bookings and events with eventStatus canceled are included; pass includeCanceled=false, 0, or no to exclude them. Soft-deleted events are excluded from the list by default; pass includeDeleted=true, 1, or yes to include them (isDeleted: true). updatedSince without startDate includes past events changed since that timestamp.

Date behavior: If no startDate is set, the response includes all future events starting today. Example: /v1/group/events

To include past events, provide a startDate. Example: /v1/group/events?startDate=2025-01-01

If both startDate and endDate are set, the response is limited to events within that date range. Example: /v1/group/events?endDate=2025-12-31&startDate=2025-01-01

Get Event by ID

GET /v1/group/events/:id

Scope: read:events

Query: extend (same as list events)

Path params: id

Returns one event object (Output: Event); same shape as list items. Canceled and soft-deleted events are always included on the single-event endpoint (isCanceled / isDeleted).

Create Event

POST /v1/group/events

Scope: write:events

Body: Request schema Input: Event (not the GET event shape). Summary: Write operations.

Response: 201 with data.id and data.slug.

Update Event

POST /v1/group/events/:id

Scope: write:events

Path params: id (event id)

Body: Input: Event (partial update). Do not send GET-only fields from Output: Event.

Response: 200 with data.id and data.slug.

Examples

GET tickets, marketing, and capacities (extend=tickets,marketing):

{ "data": { "_id": "eventId", "bookingCapacity": "Seated 120", "isCanceled": false, "isDeleted": false, "updatedAt": "2026-08-13T10:00:00.000Z", "venue": { "_id": "venueId", "name": "Hall", "capacity": 1500, "isVerified": true }, "bookings": [{ "itinerary": { "marketing": { "materials": [{ "_id": "fieldId", "description": "A3 posters", "type": "count", "sequenceNr": 1, "value": 50 }], "receiverContacts": [{ "name": "Delivery Contact", "company": "Promo GmbH", "addressLine1": "Lagerstrasse 1", "postalCode": "8005", "city": "Zurich", "country": "CH", "email": "delivery@promo.ch", "phoneNr": "+41000000001" }] }, "tickets": [{ "_id": "ticketId", "description": "GA", "sales": 25, "price": 35, "priceCurrency": "CHF", "status": "tickets", "isSoldOut": false, "sequenceNr": 1 }] } }] } }

POST create with ticket category and show capacity:

{ "title": "Openair", "startDate": "2026-09-01", "bookingCapacity": "Seated 120", "bookings": [{ "artistId": "artistId", "itinerary": { "tickets": [{ "description": "GA", "price": 35, "priceCurrency": "CHF", "status": "tickets" }] } }] }

POST /:id replace the full ticket list (include _id to keep a stored ticket id; omitted rows are removed):

{ "bookings": [{ "itinerary": { "tickets": [{ "_id": "ticketId", "description": "GA", "sales": 40, "price": 35, "priceCurrency": "CHF" }, { "description": "VIP", "price": 80, "priceCurrency": "CHF", "sales": 2 }] } }] }

POST /:id replace marketing materials and delivery contacts:

{ "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" }] } } }] }

Delete Event

DELETE /v1/group/events/:id

Scope: write:events

Path params: id

Soft-deletes the event. Response: 200 with data.success: true.

List Contacts

GET /v1/group/contacts

Scope: read:contacts

Query: limit, offset, name, updatedSince

Sync filter: createdAt OR updatedAt >= updatedSince

Get Contact by ID

GET /v1/group/contacts/:id

Scope: read:contacts

Path params: id

Create Contact

POST /v1/group/contacts

Scope: write:contacts

Body: Input: Contact — name or company required. Optional billingAddress sets the separate billing address.

Response: 201 with data.id and data.slug.

Update Contact

POST /v1/group/contacts/:id

Scope: write:contacts

Path params: id

Body: Input: Contact (partial; not the GET contact object). billingAddress: null removes the billing address. null on a billing field clears that field only.

Response: 200 with data.id and data.slug.

Delete Contact

DELETE /v1/group/contacts/:id

Scope: write:contacts

Path params: id

Soft-deletes the contact. Response: 200 with data.success: true.

List Venues

GET /v1/group/venues

Scope: read:venues

Query: limit, offset, name, updatedSince

Sync filter: createdAt OR updatedAt >= updatedSince

Get Venue by ID

GET /v1/group/venues/:id

Scope: read:venues

Path params: id

Create Venue

POST /v1/group/venues

Scope: write:venues

Body: Input: Venue — name, city, country required.

Response: 201 with data.id and data.slug.

Update Venue

POST /v1/group/venues/:id

Scope: write:venues

Path params: id

Body: Input: Venue (partial; not the GET venue object).

Response: 200 with data.id and data.slug.

Delete Venue

DELETE /v1/group/venues/:id

Scope: write:venues

Path params: id

Soft-deletes the venue. Response: 200 with data.success: true.

Output: Artist (GET)

Response shape for GET /v1/group/artists and GET /v1/group/artists/:id (ApiArtistItem). Read-only — there are no artist write endpoints. See Input vs output schemas.

Field Type Description
_id string Artist id.
appleMusic string | undefined Apple Music URL when set.
attachments object[] Attachments except profile picture. See artist attachment object.
backgroundColor string | undefined Agency-specific listing color when configured.
biography string | undefined Long biography text.
description string | undefined Short description or intro.
facebook string | undefined Facebook URL.
genres string[] Genre names.
instagram string | undefined Instagram URL.
musicLabel string | undefined Label name when set.
name string Display name.
onescreener string | undefined Onescreener URL when set.
otherLink string | undefined Additional link.
profilePicture string | undefined Profile image URL when set.
settings object | undefined Agency commission settings when configured.
slug string URL slug.
soundcloud string | undefined SoundCloud URL.
soundLink string | undefined Generic audio link when set.
spotify string | undefined Spotify URL.
tikTok string | undefined TikTok URL when set.
types string[] Artist types (extended types when present).
updatedAt string Last change time (ISO 8601).
version number Document version.
website string | undefined Website URL.
x string | undefined X/Twitter URL.
youTube string | undefined YouTube URL.

Artist attachment object (attachments[])

Field Type Description
category string Attachment category.
fileUrl string | undefined URL of the file.

Output: Contact (GET)

Response shape for contact list/detail (ApiContactItem). Differs from Input: Contact — many fields below are server-managed or mapped only on read. Writable fields on POST are listed in the input section.

GET-only (not in POST body): _id, slug, version, updatedAt, createdAt, agencyId, artistId, additionalInfo, additionalNotes, isDeleted. Writable on POST: same fields as Input: Contact (including contactType and position). Phones: phoneNr (primary/mobile), phoneNrOther (secondary/landline). mobilePhoneNr is deprecated on GET (duplicate of phoneNr).

Field Type Description
_id string Contact id.
company string | null Company name when set.
additionalInfo string | null Extra info text when set.
additionalNotes string | null Additional notes when set.
addressLine1 string | null Address line 1.
addressLine2 string | null Address line 2.
agencyId string | null Owning agency id when set.
artistId string | null Related artist id when set.
billingAddress object | null Separate billing address when set; null when none. When present, every field is included: addressLine1, addressLine2, city, company, country, name, postalCode, state (each string | null). Writable on POST — see Input: Contact.
city string | null City.
contactNr string | null Contact or reference number.
contactType string | null Category label.
country string | null Country.
createdAt string | null Creation time (ISO 8601) when set.
email string | null Email address.
isDeleted boolean Soft-delete flag. Always present.
language string | null Preferred language when set.
name string | null Display name.
notes string | null Notes.
otherLink string | null Additional URL when set.
mobilePhoneNr string | null Deprecated. Same value as phoneNr; use phoneNr instead.
phoneNr string | null Primary / mobile phone (from stored mobilePhoneNr).
phoneNrOther string | null Secondary / landline phone (from stored phoneNr).
position string | null Role or job title.
postalCode string | null Postal code.
slug string | null URL slug.
state string | null State or region.
updatedAt string | null Last change time (ISO 8601).
vatNr string | null VAT number.
version number | null Document version.
website string | null Website URL from the stored website link. A contact update that omits website and otherLink leaves this and any additional links unchanged.

Output: Venue (GET)

Response shape for venue list/detail (ApiVenueItem). Differs from Input: Venue — includes map, logo, status, and integer capacity. Street is stored as separate streetName and streetNumber; GET also includes combined street (this is the address line — v1.4.8 addressLine1 is not returned). Venue files appear as logo plus attachments (same shape as event attachments; empty list is []). On events, the nested venue object uses this shape (including Optune-verified locations used on that booking). GET /v1/group/venues returns only the group's own venues, not the full verified directory. On POST use venueId instead.

GET-only (not in POST body): _id, slug, version, updatedAt, street, locality, localityFull, logo, attachments, map, placeId, status, type, visibility, isPlace, isPublic, isVerified.

Field Type Description
_id string Venue id.
attachments object[] Venue files. Same shape as event attachments (category, public_id, url); empty list is [].
capacity integer | null Capacity (integer ≥ 0).
city string | null City.
country string | null Country.
description string | null Description.
email string | null Email.
isPlace boolean Whether the venue is modeled as a place. Always present.
isPublic boolean True when visibility is public. Private venues, including every venue created by this API, are false.
isVerified boolean True only when both verifiedAt and verifiedBy are set. A public venue can be used on another group's event only when it is verified.
locality string | null Short locality label.
localityFull string | null Full locality string.
logo object | null Logo file; null if none (category, public_id, url).
map object | null Map marker and optional viewport geometry.
name string Display name.
phoneNr string | null Phone number.
placeId string | null External place id when available.
postalCode string | null Postal code.
slug string URL slug.
status string Venue status (enum).
street string | null Street as one combined line.
streetName string | null Street name as stored in the location components.
streetNumber string | null Street number as stored in the location components.
type string Venue type (enum).
updatedAt string Last change time (ISO 8601).
venueType string[] Free-form venue type labels. Always present; empty list is [].
visibility string private or public. Venues created by this API are private. isPublic mirrors this value.
version number Document version.
website string | null Website URL.

Attachment object (logo)

category (string), public_id (string), url (string).

Output: Event (GET)

Response shape for event list/detail (ApiEventItem). Differs from Input: Event — do not round-trip a GET body into POST. startDate and endDate are calendar dates (YYYY-MM-DD). The lineup is bookings. Ticket sales live on bookings[].itinerary.tickets when extend=tickets. Event-root tickets is deprecated (legacy mirror); prefer the itinerary path.

GET serialization: every field of the resource shape is always present. Stored values (including empty string '') are returned as-is. Empty lists stay []. Missing, null, or undefined scalars are JSON null. Keys are never omitted.

Shared with POST (same names, read back after write): title, startDate, endDate, bookingCapacity, description, doors, eventType, internalComment, website, and much of bookings[] except fields listed below.

GET-only on the event: _id, slug, version, updatedAt, type, eventStatus, artwork, attachments, fee, feeCurrency, managerFee, managerFeeCurrency, isCanceled, isClosed, isDeleted, isOk, nested venue (write venueId). Booking deal is also GET-only.

Field Type Description
_id string Event id.
artwork object | null Primary flyer or cover; null if none. See attachment object.
attachments object[] Further files. See attachment object.
bookingCapacity string | null Show capacity for this booking. Separate from nested venue.capacity. Unknown is null, not 0.
bookings object[] Lineup. See booking object.
description string | null Public description.
doors string | null Doors or opening time label.
endDate string | null Last event day (YYYY-MM-DD) or null.
eventStatus string Event status (string enum).
eventType string | null Stored Optune event type label, such as Festival. Writable. Always present; null when unset. Distinct from type.
fee number | null Read-only sum of billable booking artist fees (calculateTotalFee artistFee.amount). Declined and canceled bookings are excluded. null when none are visible.
feeCurrency string | null Shared artistFee.currency of the bookings in fee. null when none are visible or the currencies differ.
internalComment string | null Internal event note. Not gated by read:internalComment.
isCanceled boolean Canceled flag.
isClosed boolean Closed flag.
isDeleted boolean Deleted flag.
isOk boolean OK or confirmed-style flag.
managerFee number | null Read-only sum of billable booking total commissions (calculateTotalFee totalCommission.amount). null when none are visible.
managerFeeCurrency string | null Shared totalCommission.currency of the bookings in managerFee. null when none are visible or the currencies differ.
slug string URL slug.
startDate string First event day (YYYY-MM-DD).
tickets ApiTicketsItem[] Deprecated. Use bookings[].itinerary.tickets (ApiTicketsItem) instead. Not writable on POST.
title string | null Title.
type string Event document type, such as Simple Booking or Manager Booking. Not the writable eventType label.
updatedAt string Last change time (ISO 8601).
venue object | null GET only. Nested venue when present; null otherwise. Same shape as Output: Venue, including full address, coordinates (map), capacity, type, and isVerified for Optune-verified locations used on the booking. isVerified is true only when both verifiedAt and verifiedBy are set. isPublic is true when visibility is public. A private venue (visibility private) appears here only when the group belongs to that venue. A public venue from another group appears here only when the booking uses an active Optune-verified venue. Verified directory venues are not listed on GET /v1/group/venues — that list is venues where the group is owner, manager, or viewer. On POST use venueId. Venue create always stores a private venue.
version number Document version.
website string | null Public website URL.

Attachment object (artwork, attachments[], venue logo / attachments[])

Field Type Description
category string Attachment category.
public_id string Stable file id.
url string File URL.

Booking object (bookings[])

GET-only on bookings: _id, artistName, artistSlug, deal, fee, feeCurrency, playStartDate, playEndDate, contacts[] (always present; populated with extend=contacts, otherwise []). deal is the mapped Optune deal, or null when deal data is hidden. Writable booking fields: see Input: Event.

Field Type Description
_id string Booking id.
artistId string Artist id.
artistName string Artist name.
artistSlug string Artist slug.
contacts object[] See booking contact object.
deal ApiDealItem | null Mapped Optune deal for this booking: currency, artistFeeGuaranteed, artistFeeAdditional, artistFeeExpenses, agencyCommission, managerCommission, otherCommission, stored dealType (or null), and calculated total. Guaranteed and additional fees include amount, currency, note, and payment fields. They omit feeDescription, afterCommissions, and vat, plus feeType, label, hint, percentage, inclusive, isFixAmount, and isSubtotalCommission. Expenses add afterCommissions and still omit feeDescription and vat. Commissions include percentage and vat, plus inclusive, isFixAmount, and isSubtotalCommission. Commission amount is the stored amount when isFixAmount is true, and null for a percentage commission. feeDescription is only on otherCommission. Commission lines omit feeType, label, hint, and afterCommissions. Calculated commission amounts stay on total.commissions (agency commission, manager commission, other commission) and total.totalCommission. Commission VAT is calculated into total (for example totalFee.vat and commissionVAT). Every total amount has percentage (a number, or null) and percentageCommissionType (single commission, or various commissions when commission VAT rates are mixed). Mixed rates on total.commissionVAT and total.commissionOnSubtotalVAT use percentage: null and percentageCommissionType: "various commissions". total.totalFee, total.artistFee, and total.expenses carry amount and currency and omit label and hint. Same visibility as fee: API key read:deal, plus member or manager deal rights (including per-artist visibility). Hidden deal data is null. Not controlled by extend.
fee number | null Read-only artist fee, deal.total.artistFee.amount from calculateTotalFee. Not the stored booking fee. null when the deal is hidden or not loaded. Requires read:deal and the same deal rights as deal.
feeCurrency string | null Currency of fee, deal.total.artistFee.currency. null when the deal is hidden or not loaded.
internalAgencyComment string | null Internal agency note. null unless the key has read:internalComment. read:deal does not reveal it.
internalComment string | null Booking internal comment. Not gated by read:internalComment.
isPublished boolean Published flag.
itinerary ApiItineraryItem Always present. extend fills category data; other slices are [] / null. See Output: Itinerary.
managerFee number | null 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 string | null Currency of managerFee, deal.total.totalCommission.currency. null when the deal is hidden or not loaded.
operationalPeriod object | null Operational window when any part is set. See operational period object.
playDuration string | null Play duration label.
playEndDate string | null Play end date in UTC (YYYY-MM-DD) or null.
playEndTime string | null End time label.
playStartDate string | null Play date in UTC (YYYY-MM-DD) or null.
playStartTime string | null Start time label.
status string idea, pending, confirmed, declined, or canceled.
statusCommentCanceled string | null Cancellation reason or comment when the booking is canceled.

Operational period (ApiBookingOperationalPeriodInput on POST; same fields on GET)

Field Type Description
endDate string | null End date (YYYY-MM-DD, UTC).
endTime string | null End time (HH:mm, UTC).
startDate string | null Start date (YYYY-MM-DD, UTC).
startTime string | null Start time (HH:mm, UTC).

Booking contact object (ApiBookingContactItem)

On bookings[].contacts[] when extend=contacts, and on bookings[].itinerary.{artistCareContacts,promoterContacts,technicalContacts,otherContacts}. Same fields as Output: Contact (ApiContactItem), plus itinerary category. phoneNr = mobile; phoneNrOther = landline. otherPhoneNr is not used on this shape.

Field Type Description
category string Extra vs Output: Contact. Itinerary contact category (artist care, promoter, technical, other).

Output: Itinerary (ApiItineraryItem, GET)

On bookings[].itinerary. Typed as ApiItineraryItem in types/api.data.types.ts. POST uses Input: Itinerary (ApiItineraryInput) — not this shape.

Every itinerary field is always present. extend populates matching slices; unrequested lists are [], scalars are null, has* flags are false. Empty string '' is kept. Nested list rows include every property; unused values are null.

GET-only: all has* flags; list row sequenceNr (assigned from array order). Every itinerary list row always includes _id (flights, guest list, hotels, lineup, marketing materials, schedules, specials, transports, travels, and tickets).

Top-level (ApiItineraryItem)

Field Type Description
hasContacts boolean Always boolean. true with extend=contacts when contacts exist; otherwise false.
hasCustom boolean Always boolean. true with extend=custom when custom data exists; otherwise false.
hasFlights boolean Always boolean. true with extend=flights when flights exist; otherwise false.
hasGuestList boolean Always boolean. true with extend=guestList when guest list exists; otherwise false.
hasHotel boolean Always boolean. true with extend=hotels when hotels exist; otherwise false.
hasItinerary boolean Always boolean. true when any extended slice is loaded; otherwise false.
hasLineup boolean Always boolean. true with extend=lineup when lineup exists; otherwise false.
hasMarketingMaterials boolean Always boolean. true with extend=marketing when marketing exists; otherwise false.
hasSchedules boolean Always boolean. true with extend=schedules when schedules exist; otherwise false.
hasSpecials boolean Always boolean. true with extend=specials when specials exist; otherwise false.
hasTickets boolean Always boolean. true with extend=tickets when tickets exist; otherwise false.
hasTransport boolean Always boolean. true with extend=transports when transports exist; otherwise false.
hasTravels boolean Always boolean. true with extend=travels when travels exist; otherwise false.
artistCareContacts ApiBookingContactItem[] Always present. Populated with extend=contacts; otherwise []. Same fields as Output: Contact plus category.
artistCareContactsNotes string | null Artist care notes.
custom object Always present: { teamConstellation: string | null }.
flights FlightItem[] Same fields as ApiItineraryFlightInput + _id (always present), sequenceNr. Always present as [] when empty or not extended. Each row includes every property; unused values are null.
guestList GuestListItem[] Same as input + _id (always present), sequenceNr, addedAt, addedBy. Always [] when empty or not extended. Each row includes every property; unused values are null.
guestListSpotCount number | null Total spots.
hotels HotelItem[] Same as ApiItineraryHotelInput + _id (always present), sequenceNr. Always [] when empty or not extended. Each row includes every property; unused values are null.
lineup LineupItem[] Same as input + _id (always present), sequenceNr. Always [] when empty or not extended. Each row includes every property; unused values are null.
marketing ApiItineraryMarketing Always present. Empty state: materials: [], receiverContacts: []. materials rows include every property (_id, description, type (text | count | choice), sequenceNr, value); unused values are null. GET returns marketing delivery contacts as receiverContacts (full ApiContactItem list). POST writes the same list as receiverContacts (see Input: Itinerary).
otherContacts ApiBookingContactItem[] Always present. Populated with extend=contacts; otherwise []. Same fields as Output: Contact plus category.
otherContactsNotes string | null Other contacts notes.
promoterContacts ApiBookingContactItem[] Always present. Populated with extend=contacts; otherwise []. Same fields as Output: Contact plus category.
promoterContactsNotes string | null Promoter notes.
schedules ScheduleItem[] Same as input + _id (always present), sequenceNr. Always [] when empty or not extended. Each row includes every property; unused values are null.
specials SpecialItem[] Same as input + _id (always present), sequenceNr. Always [] when empty or not extended. Each row includes every property; unused values are null.
technicalContacts ApiBookingContactItem[] Always present. Populated with extend=contacts; otherwise []. Same fields as Output: Contact plus category.
technicalContactsNotes string | null Technical notes.
tickets ApiTicketsItem[] Always present as [] when empty or not extended. Each row includes every property (_id, description, sales (sold count; null if uncaptured), price, priceCurrency, status, link, notes, isSoldOut, sequenceNr); unused values are null.
transports TransportItem[] Same as input + _id (always present), sequenceNr. Always [] when empty or not extended. Each row includes every property; unused values are null.
travels TravelItem[] Same as input + _id (always present), sequenceNr. Always [] when empty or not extended. Each row includes every property; unused values are null.

Edge cases & validation notes

  • Event dates: endDate must be on or after startDate on create and update (returns 400; no silent clamp).
  • Uncaptured numbers: missing ticket sales and missing show/venue capacity are null (unknown), never 0.
  • Venue capacity: integer ≥ 0 on POST and GET (e.g. 250, not "250"). Unknown venue capacity is null.
  • Venue venueType: optional string array of free-form labels on POST. On GET the field is always present (empty list is []).
  • Venue name on update: name: "" is ignored (no change); name: null returns 400.
  • Slug reuse: after DELETE, the slug stays reserved; creating a venue with the same name may get a -1 suffix.
  • Malformed JSON: invalid request bodies return 400 with reason: "Invalid JSON in request body".
  • Itinerary row _id: every itinerary list row always includes _id on GET. POST accepts optional _id to keep the stored id. Resource and contact ids are unchanged.
  • List includeDeleted: default is to omit soft-deleted events. Pass includeDeleted=true, 1, or yes to include them. GET by id always returns a soft-deleted event (isDeleted: true). POST update of a soft-deleted event returns 404.
  • Booking cancel vs event status: a canceled booking does not automatically set eventStatus / isCanceled on the event — check both when syncing.
  • Itinerary lists: sending a list (tickets, schedules, marketing.materials, and similar) replaces that list as-is. Optional _id keeps the stored row id. See Updating itinerary lists.
  • Event tickets (deprecated): root-level tickets on ApiEventItem is legacy. Read and write ticket sales via bookings[].itinerary.tickets (extend=tickets on GET; bookings[0].itinerary.tickets on POST).
  • Contact phones: use phoneNr (mobile) and phoneNrOther (landline) on POST/GET. otherPhoneNr was renamed to phoneNrOther. mobilePhoneNr on GET is deprecated (same as phoneNr).

OpenAPI

The embedded reference below points to the updated machine-readable spec: /openapi/v1.json