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:

  1. 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
    
  2. 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.