TL;DR / Executive Summary
Berlin’s Museum Sunday is a fantastic event where museum entry is free, but tickets are notoriously hard to get. The booking website gets overwhelmed, and slots vanish in seconds. To get an edge, I used my browser’s developer tools to figure out how their booking system’s API works and wrote a simple Ruby script to query it directly. This script bypasses the slow web interface and instantly shows a list of every available time slot for a given date.
The Problem: A Race Against the Clock
Once a month, Berlin’s museums open their doors for free. It’s a wonderful opportunity, but the demand for tickets is immense. When the tickets are released online, thousands of people rush to the booking portal. The site slows down, popular museums are booked within minutes, and the process of manually checking each museum for an open slot is tedious and often fruitless.
As an engineer, I knew there had to be a more efficient way. I decided to see if I could talk to the booking system directly, bypassing the web UI.
The Solution: Reverse Engineering the API
The key to this project was to understand how the booking website fetches its data. The process, often called “reverse engineering,” involves watching the network traffic between your browser and the server to understand their communication protocol.
I used the Network tab in my browser’s developer tools and performed the steps to find a ticket. I quickly discovered that the website wasn’t rendering the data on the server; it was making calls to a modern JSON API. This was the breakthrough.
I identified two key API endpoints:
- Get All Tickets: The first endpoint provides a list of all possible museum tickets for a specific date.
https://kpb-museum.gomus.de/api/v4/tickets - Get Capacities: The second endpoint takes a list of ticket IDs from the first call and returns the number of available slots for every time window.
https://kpb-museum.gomus.de/api/v4/tickets/capacities
With these two endpoints, I had everything I needed to build a script that could find tickets.
The Logic: A Three-Step Process
I wrote a script to automate this, and here’s the core logic presented as Ruby-style pseudocode. It outlines the steps without getting lost in specific library implementations.
# Ruby-Style Pseudocode
# --- STEP 1: Fetch all museum and ticket data for a given date ---
target_date = '2024-08-04'
tickets_url = "https://.../api/v4/tickets?valid_at=#{target_date}"
# Make an HTTP GET request and parse the JSON response
all_tickets_data = http_get(tickets_url).parse_json
# Create a hash to map museum IDs to their names, e.g., { 123 => "Pergamon Museum" }
museum_map = all_tickets_data.each_with_object({}) do |ticket, hash|
hash[ticket['quota_id']] = ticket['title']
end
# Collect all the unique ticket IDs for the next API call
all_ticket_ids = all_tickets_data.map { |ticket| ticket['id'] }
# --- STEP 2: Fetch the available capacities for all tickets at once ---
capacities_url = "https://.../api/v4/tickets/capacities?date=#{target_date}"
# Append all ticket IDs to the URL as query parameters
capacities_url += build_query_string_for_ids(all_ticket_ids)
# Make the second HTTP GET request
capacities_data = http_get(capacities_url).parse_json
# --- STEP 3: Iterate and display the results ---
museum_map.each do |museum_id, museum_name|
# Find the capacity info for the current museum
museum_capacity = capacities_data['data'][museum_id.to_s]
# Skip if there's no data or if all time slots are empty
next if museum_capacity.nil? || museum_capacity['capacities'].values.sum.zero?
puts "--- #{museum_name} ---"
# Loop through each time slot and print the ones with available tickets
museum_capacity['capacities'].each do |timestamp, available_tickets|
next if available_tickets.zero?
# Format the time for readability
time_slot = format_time(timestamp)
puts " #{time_slot} Tickets Available: #{available_tickets}"
end
end
This logic efficiently queries the API and presents a clean, simple list of every available ticket, turning a frantic clicking race into a simple command-line script.
Conclusion
This was a fun and practical project. By spending a few minutes investigating the website’s API, I was able to build a tool that solves a real-world problem. It’s a great example of how a little bit of automation can save a lot of time and frustration. Now, instead of frantically clicking through a slow website, I can just run the script and instantly see where the tickets are.