DEV Community

José Catalá
José Catalá

Posted on

Your Flight Was Cancelled. Here's How I Built a Tool That Finds Every EU261 Claim at Any Airport

Most passengers never claim the compensation they're legally owed. I built a feature that changes that — here's how it works under the hood.


The €250 Nobody Asks For

When Lufthansa cancelled flight LH954 from Frankfurt to Birmingham this morning, 25 flights at that airport alone became eligible for compensation under EU261 in the last 7 days.

Most of those passengers won't claim a cent.

Not because they're ineligible — under EU Regulation 261/2004, a cancelled flight on a route under 1,500 km entitles each passenger to €250 in cash compensation, regardless of whether the airline refunds the ticket. It's not a voucher. It's not store credit. It's money.

The problem isn't eligibility. It's awareness. Most travellers don't know the regulation exists, and those who do often assume claiming is too complicated to bother with.

I built MyAirports — a real-time airport data API — to solve the scraping and data normalisation problem. But once I had clean, reliable flight status data for hundreds of airports, a second problem became obvious: all this data about delays and cancellations was sitting there unused. I was already tracking which flights were cancelled, their distances, their routes. The EU261 calculation is basically a lookup table. So I built the Claims tab.

Here's how it works.


EU261 in Two Minutes

EU Regulation 261/2004 applies when:

  • The flight departs from an EU airport, or arrives at an EU airport on an EU/UK carrier
  • The flight is cancelled or delayed by more than 3 hours at arrival
  • The disruption is not caused by "extraordinary circumstances" (bad weather, air traffic control strikes, etc.)

Compensation tiers by route distance:

Distance Compensation
Under 1,500 km €250
1,500–3,500 km €400
Over 3,500 km €600

UK passengers still have equivalent rights post-Brexit under UK261.

That's it. The airline cannot refuse if the criteria are met. They can offer a voucher — but you're entitled to cash.


The Data Problem I Already Had Solved

MyAirports scrapes live flight data from airport websites — the actual departure boards, not third-party aggregators. Every flight gets:

  • IATA flight code
  • Origin and destination airport (with IATA codes)
  • Scheduled and actual departure/arrival times
  • Real-time status: on_time, delayed, cancelled, diverted, landed, etc.
  • Airline name

The status normalisation work was significant — airport websites display cancellations in seven different languages with wildly inconsistent terminology. I wrote about that in a previous post. By the time a flight hits the database, it has a clean, normalised status field.

For the Claims feature, cancelled is the key trigger. But not all cancellations qualify — the regulation only covers flights that departed from or arrived at an EU/UK airport. Since I'm already storing IATA codes, I can check country eligibility instantly against a static lookup of EU member state airport codes.


Calculating Eligibility Automatically

The core logic is straightforward once the data is clean:

function getEU261Compensation(flight) {
  // Must be cancelled or delayed 3+ hours
  if (flight.status !== 'cancelled' && flight.delayMinutes < 180) {
    return null;
  }

  // Route must touch EU/UK
  const fromEU = isEUAirport(flight.origin);
  const toEU = isEUAirport(flight.destination);
  const isEUCarrier = isEUAirline(flight.iataCode.slice(0, 2));

  if (!fromEU && !(toEU && isEUCarrier)) {
    return null;
  }

  // Distance-based compensation
  const distance = getGreatCircleDistance(flight.origin, flight.destination);

  if (distance < 1500) return 250;
  if (distance < 3500) return 400;
  return 600;
}
Enter fullscreen mode Exit fullscreen mode

getGreatCircleDistance uses stored lat/lon for each IATA airport code — a ~7,000-entry lookup table I pre-compiled from public data. The great-circle calculation is just the haversine formula:

function haversine(lat1, lon1, lat2, lon2) {
  const R = 6371; // Earth radius in km
  const dLat = toRad(lat2 - lat1);
  const dLon = toRad(lon2 - lon1);
  const a =
    Math.sin(dLat / 2) ** 2 +
    Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
  return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}
Enter fullscreen mode Exit fullscreen mode

This runs at query time — no pre-computation needed. The distance between any two airports is deterministic, so it could be cached, but it's cheap enough that it doesn't matter.


What the Claims Tab Shows

When you open any airport page on myairports.online and click Claims, you see every flight in the last 7 days that passes the eligibility check — ranked by date, with:

  • Flight number and airline
  • Route (origin → destination)
  • Status (Cancelled / Delayed)
  • Distance (so you can verify the tier yourself)
  • Compensation amount (€250 / €400 / €600)
  • Regulation (EU261 or UK261)
  • Claim action — either a direct claim link to the airline's own portal, or a one-click claim via an integrated claims service

Birmingham Airport (BHX) had 25 flights eligible for compensation in the last 7 days when I last checked. That's not unusual. At a mid-sized European hub, 20–30 eligible flights per week is typical in normal operating conditions. During bad weather events or industrial action, that number can spike into the hundreds.


Two Claim Paths

Not every airline makes it easy to claim directly. Some (like British Airways and Lufthansa) have functional online claim forms — they comply, slowly. Others have forms that are buried, broken, or deliberately tedious.

The Claims tab surfaces both options:

Direct airline claim — for carriers with reliable self-service portals (EasyJet, Air France, Ryanair). We link directly to their claim page with the flight details pre-filled where possible.

Assisted claim for €2.99 — for cases where direct claiming is harder, or where passengers just don't want to deal with it. A claims management service handles the entire process: paperwork, follow-up, escalation to alternative dispute resolution if the airline refuses. They charge a flat €2.99 rather than the typical 25–35% cut that most "no win no fee" services take.

The decision of which path to offer is driven by a per-airline lookup table I maintain — essentially a reliability score for each carrier's own claims process. If the airline consistently pays out promptly on direct claims, we show the direct link. If they're known to stall, the assisted option is surfaced first.


The Unexpected Use Case

I built the Claims tab primarily as a user-facing feature. But it turns out developers using the API have found it useful too.

The /airports/{iata}/claims endpoint returns the same structured data:

{
  "airport": "BHX",
  "period_days": 7,
  "eligible_flights": [
    {
      "flight": "LH954",
      "airline": "Lufthansa",
      "date": "2026-04-10",
      "origin": "FRA",
      "destination": "BHX",
      "status": "cancelled",
      "distance_km": 766,
      "compensation_eur": 250,
      "regulation": "EU261",
      "claim_url": "https://www.lufthansa.com/gb/en/flight-irregularity-claim"
    }
  ],
  "total_eligible": 25
}
Enter fullscreen mode Exit fullscreen mode

A few travel agencies and corporate travel tools have integrated this to automatically flag claims for their customers after disrupted trips. The value proposition is simple: most business travellers don't track their own delays. If a tool can surface "your employee on LH954 is owed €250" after the fact, that's real money recovered with zero effort.


What I Learned

Three things stood out building this:

Cancellation data is noisy. Airport websites sometimes mark a flight as cancelled and then re-list it under a new flight number (a "rescue flight"). If you don't de-duplicate carefully, you'll show a claim for a flight that technically departed. I added a reconciliation step that checks if the same route, same date, and similar departure time appears with a landed status anywhere in the dataset.

Extraordinary circumstances are unverifiable at query time. The regulation exempts cancellations caused by things outside the airline's control — storms, ATC strikes, bird strikes. I don't attempt to classify this automatically. The Claims tab shows all cancellations that pass the route/distance check and notes that eligibility is subject to circumstances. The claims process itself resolves extraordinary-circumstance disputes.

Distance matters more than people think. A Dublin→Birmingham cancellation (322 km) yields €250. A Paris CDG→Birmingham cancellation (488 km) also yields €250. But a longer intra-EU route like Lisbon→Helsinki (2,800 km) would yield €400. Passengers often assume cancelled = €250 flat, then feel cheated when their long-haul claim is assessed differently. Making the distance explicit in the UI has reduced those support queries significantly.


Try It

Every airport on myairports.online has a Claims tab. If you flew into or out of a European airport in the last 7 days and your flight was cancelled, check the Claims tab for your airport — there's a reasonable chance your flight is listed.

If you're a developer, the /airports/{iata}/claims endpoint is part of the MyAirports API. The free tier gives you 100 requests/day; the paid tier removes the cap and adds historical claims data going back 90 days.

EU261 was designed to make airlines accountable. The data to enforce it has always existed — it just needed surfacing.


MyAirports is a real-time airport data API covering 200+ airports across Europe, the Americas, and Asia-Pacific. The Claims feature is live at myairports.online.

Top comments (0)