Tours

Guided tours - list, check availability, compute prices, and trigger via request or direct booking.

A Tour in go~mus is a guided experience that does not happen on fixed dates but is scheduled on request or booking - classic guided tours, school workshops, private tours, children's birthday parties. Unlike events, there are no pre-defined dates per session; instead you query when a tour would be generally available, and the concrete date materializes only with the request or booking.

The two most common use cases for this resource are:

1. Tour overview on the website with filtering by language, audience, special exhibition

2. Request or booking form on the website that lands in go~mus (the pattern used by Museumsdienst Berlin, Hamburg and many others)

Full endpoint and schema reference: Swagger.

Tour vs Event - the difference

TourEvent
Datesflexible, proposed by the customerpredefined in the back office (dates)
Booking primitivethe tour itself plus desired date/timethe date
Main flowrequest or direct bookingdirect booking
Availability endpointstart_timesdates / calendar

Technically a date per tour exists too - once a tour is booked, an internal booking record with a concrete time is created. From the outside API view, however, the resource is the tour, not the date.

Anatomy of a tour object

The index and show responses contain per tour:

  • id - stable, use it in items of requests and orders
  • title, sub_title, description - translatable via ?locale=de
  • museum_id, exhibition_id - assignment
  • category - embedded category (tour, workshop, lecture, children's birthday, ...)
  • bookable, registerable - context-dependent flags for your auth context
  • featured - boolean, highlight flag
  • min_persons, max_persons, max_participants - per-booking limits and group cap
  • duration - duration in minutes
  • equipment - list of required equipment (e.g. headphones, projector)
  • entry_fee - mode for entry-fee display

The show response additionally embeds: tickets, quotas, limitations, validities, content (long descriptions), documents (attachments), sellabilities.

Full field list: Swagger.

Endpoints at a glance

EndpointWhatWhen to use
GET /api/v4/toursTour listOverview page with filters
GET /api/v4/tours/:idTour detailFull description, configuration
GET /api/v4/tours/:id/start_timesAvailable start times"When can I book this tour?"
GET /api/v4/tours/:id/pricesPrice calculationWhat the tour costs for X people on date Y
GET /api/v4/tours/:id/categoriesCategories of a tourfor filter UI
GET /api/v4/tours/:id/languagesAvailable languagesfor filter UI
GET /api/v4/tours/:id/audiencesAudiencesfor filter UI
GET /api/v4/tours/:id/age_groupsAge groupsfor filter UI
POST /api/v4/requestsCreate a requestCustomer asks for a date, museum confirms manually
POST /api/v4/ordersCreate a direct bookingCustomer books immediately

Common tasks

List tours

curl "https://demo.gomus.de/api/v4/tours?by_museum_ids[]=20&by_audience_ids[]=4&locale=de"

The filters mirror those for events - all optional, all combinable:

  • by_museum_ids[]=20 - tours of one house only
  • by_exhibition_ids[]=2057 - tours for one special exhibition (-1 for "no exhibition link")
  • by_category_ids[]=12 or by_categories[]=tour - category filter
  • by_audience_ids[]=4 - e.g. only tours for families
  • by_age_group_ids[]=2 - e.g. only tours for adults
  • by_grade_ids[]=8 - school grade
  • by_language_ids[]=1 - only German-language tours
  • by_catch_word_ids[], by_disablement_ids[], by_proposal_category_ids[] - further classifications
  • by_featured=true - highlights only

Populate a filter UI with available values

As with events, sub-resource endpoints return values actually used in the instance:

curl "https://demo.gomus.de/api/v4/tours/categories?locale=de"
curl "https://demo.gomus.de/api/v4/tours/audiences?locale=de"
curl "https://demo.gomus.de/api/v4/tours/languages?locale=de"

Find available start times

curl "https://demo.gomus.de/api/v4/tours/512/start_times?start_at=2026-05-01&end_at=2026-05-31&participants=15&language_ids[]=1"

Parameters:

  • start_at, end_at - time window (default: today to end of month). Maximum range: 60 days - larger windows return a validation error. For a 12-month datepicker make several calls in 60-day buckets.
  • participants - headcount, affects availability (default: 1 or the tour's min_persons)
  • language_ids[], age_group_ids[] - filters that flow into the availability calculation
  • depth - any (at least one slot per day, boolean answer) or all (every bookable slot, list); default any

depth=any is the typical datepicker case ("which days are bookable"). depth=all is the slot-picker case ("which times on the chosen day").

Compute a price

Tour prices are context-dependent - they depend on date, time, headcount, language and the price group (PriceTargetAudience, e.g. "private customer", "school class", "reseller"). The endpoint computes that in one call:

curl "https://demo.gomus.de/api/v4/tours/512/prices?date=2026-05-15&time=14:00&participants=15&language_id=1"

Parameters:

  • date (default: today)
  • time as HH:MM (default: 12:00)
  • participants (default: 1)
  • language_id (default: first language assigned to the tour)
  • price_target_audience_id (default: from auth context, otherwise the instance default PTA)

The response contains line items and the total - exactly what to display in the booking form.

Tour detail

curl "https://demo.gomus.de/api/v4/tours/512?locale=de"

The show response delivers description, embedded tickets/quotas, validity rules and attachments - enough for a full detail page.

From wish to booking - the two paths

Path 1: Request (POST /api/v4/requests)

The standard solution for school inquiries, group tours and anything the museum wants to review editorially before confirming (dispatch, guide assignment, special arrangements). The request lands in go~mus with status registered, runs through the inquiry inbox, the back office responds manually.

curl -X POST "https://demo.gomus.de/api/v4/requests" \
  -H "Authorization: Bearer YOUR_API_USER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "request": {
      "participants": 25,
      "companions": 2,
      "date": "2026-06-15",
      "notes": "Class 7a, history advanced, focus on 19th century",
      "language": "Deutsch",
      "age_group": "School class",
      "grade": "Secondary II",
      "items": [
        { "tour_id": 512, "time": "10:00" },
        { "tour_id": 519, "time": "13:00" }
      ],
      "customer": {
        "name": "Müller", "surname": "Anna",
        "email": "teacher@example-school.de",
        "phone": "+49 30 12345",
        "street": "Schulweg 1", "zip": "10115", "city": "Berlin"
      }
    }
  }'

Important:

  • Authentication via an API user token with permission. A public token is not enough.
  • items is required and must contain at least one tour_id plus the desired time. Multiple items in one request = multi-tour day.
  • payment_mode, language, age_group, grade, proposal_category, category accept either an id or a translated name.
  • skip_confirmation_email: true suppresses the automatic confirmation mail (e.g. if your system sends its own).

The response returns id, created_at, updated_at. Validation errors come as 422 with field details.

Path 2: Direct booking (POST /api/v4/orders)

When the tour should sell directly - private tour, fixed-price workshop, tickets on demand - it works like for events via order plus finalize.

Create the order with the tour as an item:

curl -X POST "https://demo.gomus.de/api/v4/orders" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "order": {
      "items": [
        {
          "tour_id": 512,
          "date": "2026-06-15",
          "start_time": "14:00",
          "participants": 8,
          "language_id": 1
        }
      ],
      "customer": { "email": "user@example.com", "..." : "..." }
    }
  }'

Then finalize:

curl -X POST "https://demo.gomus.de/api/v4/orders/ORDER_TOKEN/finalize" \
  -H "Authorization: Bearer YOUR_TOKEN"

Exact payload structures, validation rules and payment options: Swagger and Best Practices.

Request vs direct booking - when to use what

CriterionRequestDirect booking
Immediately binding?No, manual confirmationYes
Price fixed at submit?Optional (group prices auto-applied)Yes
Payment at submit?NoYes, in finalize
Typical useSchool classes, group tours, special wishesPrivate tours, workshop tickets
AuthAPI user token with permissionPublic token, reseller token or API user

If you offer both, the pattern is: form on the website with a "Request" button for schools/groups and a "Book now" button for individuals.

Relationships

Tour
 ├── Category ............ e.g. tour, workshop, lecture
 ├── Museum .............. house
 ├── Exhibition (0..1) ... special exhibition
 ├── Audience (0..n) ..... e.g. families, school classes
 ├── AgeGroup (0..n) ..... e.g. children, adults
 ├── Language (0..n) ..... languages offered
 ├── Guide (0..n) ........ qualified guides
 ├── Equipment (0..n) .... required equipment
 ├── Request (via items)  if submitted as a request
 └── OrderItem .......... if booked directly

Pitfalls

  • Requests need an API user token. Unlike tour listing or price calculation (public token suffices), POST /requests requires an authenticated API user with permission. Do not expose that token in the frontend - the request must be relayed server-side.
  • Call start_times with participants. Without it you get availability for the tour's min_persons. Not wrong, but optimistic for large groups - on booking day the group may no longer fit.
  • prices is context-dependent. Without price_target_audience_id the endpoint falls back to the default PTA (e.g. "private customer"). For resellers, school classes or members, set it explicitly or you compute the wrong tariff.
  • depth on start_times matters. any is much cheaper and enough for datepicker logic. all only pays off when displaying slots for a day.
  • start_times has a 60-day cap. For longer preview windows, make several calls. Rarely a problem in practice since datepickers usually show only the current or next month.
  • Translated names instead of ids. The request accepts language: "Deutsch" in addition to the id. Handy for forms without an id lookup, but the value must match the stored translation exactly.