DEV Community

flurin laim
flurin laim

Posted on Fully Autonomous

Why a ticket-availability monitor is a state machine, not a scraper

A ticket calendar looks like an easy automation target: request a page, search
for a date, and send an email when it appears. That implementation works until
the first queue, partial response, stale cache or provider outage. Then it can
quietly turn "I do not know" into "sold out" — or generate a false alert.

I learned this while building
MachuPing, an independent monitor
for official Machu Picchu ticket availability. I am the maker. It does not
sell, hold, reserve or buy admission; the official booking platform remains the
source of truth.

The useful abstraction is a small state machine:

UNKNOWN -> CONFIRMED_UNAVAILABLE -> RETURNED_AVAILABLE
   ^                 |                       |
   |                 v                       v
   +------------- PROVIDER_ERROR ------ ALERTED
Enter fullscreen mode Exit fullscreen mode

The exact labels will vary, but three rules matter.

1. Unknown is not unavailable

Queues, timeouts, malformed payloads and incomplete calendars are observations
about the monitor, not evidence about inventory. Persist them separately. A
provider error should never close a date or trigger a reassuring "still sold
out" message.

2. Match the user's real constraint

"Machu Picchu is available" is too broad to be useful. Inventory is split by
route, date, entry time and capacity. A valid transition requires a match for
the selected combination, including the requested party size.

This also prevents a common analytics mistake: counting every polling response
or every seat-like value as a unique ticket. A state change is a state change,
not proof of inventory volume.

3. Alert on a confirmed transition, not a snapshot

The valuable event is not simply available. It is a move from a previously
confirmed unavailable state to confirmed available. Persist an idempotency key
for that combination so retries do not create duplicate email.

Before sending, revalidate the observation when the provider permits it. The
alert should still state the limitation plainly: availability can disappear
before the traveller reaches official checkout.

A practical event record

At a module boundary, I prefer an explicit shape similar to this:

type AvailabilityObservation = {
  attractionId: string;
  productId: string;
  visitDate: string;
  entryTime: string;
  partySize: number;
  state: "available" | "unavailable" | "unknown";
  observedAt: string;
  source: "official-provider";
};
Enter fullscreen mode Exit fullscreen mode

The monitoring loop can then compare the new observation with the last
confirmed state, while the alert layer consumes only a narrowly defined
returned_available event.

That separation makes the system less exciting in a demo and much safer in
production. It also generalizes beyond tickets: appointment slots, permits,
limited reservations and restocks all benefit from treating uncertainty as a
first-class state.

If you have built a monitor against a fragile upstream calendar, I would be
interested in the failure mode that forced you to stop treating it as a simple
scrape.

Top comments (0)