# Ryanair Fare Calendar Scraper (`apt_marble/ryanair-fare-calendar-scraper`) Actor

Day-by-day cheapest fares for every date on any Ryanair route, up to a year ahead. Each date comes back with its fare, the currency it was filed in, the flight number and departure time, plus the cheapest and dearest day of every month, in one clean table.

- **URL**: https://apify.com/apt\_marble/ryanair-fare-calendar-scraper.md
- **Developed by:** [Hamza](https://apify.com/apt_marble) (community)
- **Categories:** Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 fare date collecteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Ryanair Fare Calendar Scraper

Get the cheapest fare for **every single date** on any Ryanair route, up to a year ahead, as one clean table. Pick a route, pick how far ahead to look, and every date comes back with its cheapest published fare, the currency it was filed in, the flight number and departure time behind that fare, and the cheapest and dearest day of each month already worked out for you. No account and no login are needed — just run it.

One row per date, one table, no gaps. Built for price-alert products, travel apps, route analysts and anyone who needs Europe's largest low-cost carrier in a spreadsheet rather than on a booking screen.

***

### What you can do with it

- **Find the cheapest day to fly** on a route across the next twelve months, and see how much cheaper it is than the month's average.
- **Power a price-alert or fare-watch product** by running it on a schedule and comparing each day's fare with the last run.
- **Compare a route in several currencies** — on routes between two currency zones the airline sets its own price in each one, so the cheapest day can move depending on which you sell in.
- **Feed a seasonality model** with a full year of day-level fares, so you can show when a route gets expensive and by how much.
- **Build route-level dashboards** with the monthly low, the monthly high and the day each one falls on, already computed.
- **Check what is actually operating** — every route-month the airline had nothing on sale in is named in the run summary, with the reason and whether the airline flies the route at all, so a seasonal gap is never mistaken for a missing answer.

***

### What you get

One row per route, direction and date. A real record, abridged:

```json
{
  "carrier": "Ryanair",
  "carrierCode": "FR",
  "routeId": "DUB-STN",
  "origin": "DUB",
  "originName": "Dublin Airport",
  "originCountry": "IE",
  "destination": "STN",
  "destinationName": "London Stansted Airport",
  "destinationCountry": "GB",
  "direction": "outbound",
  "date": "2026-11-01",
  "price": 23.99,
  "currency": "EUR",
  "isUnavailable": false,
  "isCheapestOfMonth": true,
  "flightNumber": "FR30",
  "departureLocalTime": "2026-11-01T06:20",
  "arrivalLocalTime": "2026-11-01T07:40",
  "departureTimeZone": "Europe/Dublin",
  "flightsThatDay": 10,
  "monthMinFare": 23.99,
  "monthMinFareDate": "2026-11-01",
  "monthMaxFare": 24.99,
  "monthMaxFareDate": "2026-11-06",
  "routeStatus": "operated",
  "searchMonth": "2026-11",
  "collectedAt": "2026-08-16T12:00:00.000Z"
}
```

***

### Input reference

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `origin` | string | `DUB` | Three-letter code of the departure airport. City-wide codes such as `LON` are not accepted — the airline sells fares per airport. |
| `destination` | string | `STN` | Three-letter code of the arrival airport. |
| `routes` | array | `[]` | Extra routes for the same run, written as `DUB-STN`. Up to 50 routes per run. |
| `tripType` | select | `oneWay` | `oneWay` collects the outbound calendar. `roundTrip` also collects the journey home, so each date appears once per direction. |
| `monthsAhead` | integer | `3` | How many calendar months to collect, counting the current one. Maximum 12. |
| `startDate` | date | today | Collect from this date instead of today. |
| `endDate` | date | — | Stop at this date. Replaces `monthsAhead` when set. |
| `currency` | select | `EUR` | The currency fares are collected in: EUR, GBP, USD, PLN, CZK, SEK, DKK, NOK, HUF, CHF or MAD. |
| `includeFlightNumbers` | boolean | `true` | Adds the flight number behind each cheapest fare, and confirms the route is operated that month. |
| `includeUnavailableDates` | boolean | `true` | Keeps a row for every date in range, including those with nothing on sale, so the calendar has no gaps. |
| `maxRouteMonths` | integer | `200` | Safety cap on run size. One route-month is one route and one calendar month. Maximum 600. |
| `maxRows` | integer | `20000` | Stop after this many dates. The run stops searching at that point, so nothing beyond the limit is collected or charged. Maximum 200,000. |

***

### Output fields

| Field | Type | Description |
| --- | --- | --- |
| `carrier`, `carrierCode` | string | Always `Ryanair` and `FR`. |
| `routeId` | string | The route this row belongs to, as `DUB-STN`. |
| `origin`, `originName`, `originCity`, `originCountry` | string | Departure airport code and its details. |
| `destination`, `destinationName`, `destinationCity`, `destinationCountry` | string | Arrival airport code and its details. |
| `direction` | string | `outbound` or `inbound`. |
| `tripType` | string | Whether this row came from a one-way or a return collection. |
| `date` | string | The departure date, as `YYYY-MM-DD`. |
| `price` | number | null | The cheapest fare published for that date. `null` when nothing is on sale. |
| `currency` | string | null | The currency the fare was actually published in — read back from the airline's own answer, never assumed from what was asked for. |
| `requestedCurrency` | string | The currency you chose for the collection. |
| `isUnavailable` | boolean | True when that date has no fare on sale. |
| `isCheapestOfMonth` | boolean | True when this date matches the month's lowest fare. |
| `routeStatus` | string | Always `operated` on a row, because a row only exists for a date the airline was selling — which is proof it flies the route that month. Months with nothing on sale produce no rows at all; each one is named in the run summary instead, with its reason and its route status. |
| `flightNumber` | string | null | The flight behind the cheapest fare. `null` when flight numbers were not collected, or when no departure that day matched. |
| `departureLocalTime`, `arrivalLocalTime` | string | null | Local airport times, as `YYYY-MM-DDTHH:mm`. |
| `departureTimeUtc` | string | null | The same departure as a UTC timestamp. |
| `departureTimeZone`, `arrivalTimeZone` | string | null | The zone each local time belongs to. |
| `flightsThatDay` | number | null | How many departures the airline lists that day, when flight numbers were collected. |
| `monthMinFare`, `monthMinFareDate` | number | string | The cheapest fare of that month and the date it falls on, as the airline publishes it for the whole month. |
| `monthMaxFare`, `monthMaxFareDate` | number | string | The dearest fare of that month and the date it falls on, on the same basis. |
| `searchMonth` | string | The month this row belongs to, as `YYYY-MM`. |
| `isEstimated` | boolean | Always `false`. Every fare here is one the airline published; nothing is modelled or filled in. |
| `collectedAt` | string | When the row was collected. |

Alongside the table, every run writes a summary you can read: dates collected, routes the airline does not fly, and — kept strictly separate — searches that could not be completed. It also lists **every route-month the airline had nothing on sale in, by name**, each with its month, the reason, and whether the airline flies that route at all:

```json
{
  "route": "DUB-CFU",
  "month": "2027-01",
  "routeStatus": "operated",
  "reason": "no_fares_published",
  "explanation": "the airline had nothing on sale in this month"
}
```

That is where a seasonal gap shows up: the route is flown, January simply has nothing on sale. A route the airline does not fly at all reads `not_operated` / `route_not_served`, and one that could not be established either way reads `unknown` — never guessed.

***

### Pricing

Pay per event. You pay for the fare dates you receive, plus a small amount for each route-month searched.

| What you pay for | Price |
| --- | --- |
| **Fare date collected** — one date, with its fare and everything attached to it | **$1.00 per 1,000** |
| **Route month searched** — one route, one calendar month | **$0.010 each** |

**A worked example.** You collect Dublin → London Stansted three months ahead, one way — the prefilled input this actor opens with.

- That is 3 route-months (1 route × 3 months) = **$0.03**
- All three months are selling, so about 90 dates come back = **$0.09**
- **Total: about $0.12**

Scaling up: ten routes over twelve months is 120 route-months ($1.20) and roughly 3,600 dates ($3.60) — about **$4.80** for a full year of day-level fares on ten routes.

A route the airline does not fly is dropped before anything is searched, and costs nothing.

*Maximum rows* is a hard cost cap, not just a cap on the table: once that many dates have been collected the run stops searching, so no further route-month is searched or charged.

A month where the airline genuinely has nothing on sale still counts as a route-month searched, because finding that out is real work and the answer is worth having. A search that could **not** be completed is never charged, and is reported in the run summary as a failed search rather than as an empty month.

***

### Limits & what this actor cannot do

- **Fares are a snapshot.** Airline pricing changes continuously, and a fare collected now may be gone minutes later.
- **It reports what the airline publishes to the public.** It does not hold seats, does not book, and cannot guarantee a fare is still available.
- **One fare per date, not every flight.** Each date carries the cheapest fare the airline is publishing for that day. Other departures on the same day are usually dearer and are not priced separately.
- **The fare is the airline's own headline price** for one adult with no extras. Baggage, seat selection, priority boarding and card fees are extra and are not included.
- **Do not re-convert the fares yourself.** On most routes the airline quotes one price and shows it to you converted at a single rate, so converting it back gains you nothing and loses the rounding. But where a route crosses two currency zones — Ireland to the United Kingdom, for example — the airline sets its own price in each currency, and those prices move independently: the same day can be a round 23.99 in one and 21.99 in the other, and the cheapest day of the month can differ between them. Collect in the currency you sell in; collect the route twice if you need to compare two, and compare the collected figures rather than a conversion.
- **The currency choice is a fixed list** — EUR, GBP, USD, PLN, CZK, SEK, DKK, NOK, HUF, CHF and MAD. Those are the currencies the airline was verified to price this calendar in. Every row also carries the currency its fare was actually published in, read back from the airline's own answer rather than assumed, so you can always tell.
- **Roughly a year ahead, no further.** The airline publishes about twelve months of fares; dates beyond that are trimmed and the run tells you it trimmed them.
- **Routes are seasonal.** A route with nothing on sale in the months you asked for is reported in the run summary as having nothing on sale, with the reason, rather than as an error — and separately from a route the airline does not fly at all, which is judged against the airline's own route list rather than against one month's timetable. A month with no fares carries no rows in the table, because the airline published no dates to build them from and none are invented.
- **A search the airline refuses to complete is reported as a failed search, never as "no fares found".** This is deliberate: an empty result that is really a refusal is the one outcome that would quietly mislead you.
- **City-wide codes are not supported.** The airline sells per airport, so `LON` or `MIL` are rejected with the list of airports to use instead.
- **Flight numbers are optional and matched, never guessed.** The flight number attached to a date is the departure whose own time matches the cheapest fare. Where no departure matches, the field is left empty rather than filled with the first flight of the day.
- **Speed depends on the size of the job** and on how quickly the airline answers. No fixed throughput is promised.
- **The airline's terms govern automated access.** You are responsible for using the data lawfully and in line with those terms.

***

### FAQ

**Do I need an account with the airline?**
No. Nothing is signed in to and no booking is ever started.

**Does it need my login or password?**
No. There is nothing to configure beyond the route and the dates.

**Can I schedule it?**
Yes. It is built for it — run it daily or weekly on a list of routes and compare each run with the last to spot drops. Day-to-day comparison is honest; minute-to-minute is not, because these fares simply do not move that fast.

**Are these fares bookable?**
They are the fares the airline was publishing at the moment of collection, for one adult with no extras. Prices change continuously and availability is not held, so treat a fare as a snapshot rather than a guarantee.

**Why does a date have no price?**
Because the airline had nothing on sale for that date — a day the route does not operate, or a day that has sold out. Those dates are kept with an empty fare so your calendar has no gaps; turn off *Keep dates with no fare* if you only want priced dates.

**Why can I not choose any currency I like?**
Because only the listed ones are priced honestly. Others are answered without any error at all, but with euro amounts or with an empty month, which would put a wrong number or a wrong verdict in your table. The list is closed so that cannot happen.

**Can I get every flight on a route, not just the cheapest one per day?**
Not from this one. It is a fare calendar: one cheapest fare per date. The number of departures that day is included so you know how much choice there is.

**What happens if one route fails and the others work?**
The successful routes are still collected and returned. The failed ones are counted separately in the run summary, so you always know whether an empty result means "no fares" or "could not be read".

# Actor input Schema

## `origin` (type: `string`):

Three-letter code of the departure airport, for example DUB for Dublin or STN for London Stansted. City-wide codes such as LON are not accepted, because the airline sells fares per airport.

## `destination` (type: `string`):

Three-letter code of the arrival airport, for example STN for London Stansted.

## `routes` (type: `array`):

Extra routes to collect in the same run, written as two airport codes, for example DUB-STN. Add as many as you need, up to 50 in one run.

## `tripType` (type: `string`):

One way collects the outbound calendar only. Return also collects the calendar for the journey home, so each date appears twice: once for each direction.

## `monthsAhead` (type: `integer`):

How many calendar months to collect, counting the current one. The airline publishes roughly a year of fares, so anything beyond twelve months comes back empty.

## `startDate` (type: `string`):

Collect from this date instead of today. Written as YYYY-MM-DD. Leave empty to start today.

## `endDate` (type: `string`):

Stop at this date. Written as YYYY-MM-DD. When set, it replaces the months-to-look-ahead setting.

## `currency` (type: `string`):

The currency fares are collected in. On routes crossing two currency zones the airline sets its own price in each, so the cheapest day of a month can differ by currency; elsewhere it shows one price converted. Every row states the currency its fare was actually published in.

## `includeFlightNumbers` (type: `boolean`):

Attach the flight number of the cheapest departure on each date, and confirm the route is actually operated in that month.

## `includeUnavailableDates` (type: `boolean`):

Keep a row for every date in range, including those with nothing on sale, so the calendar has no gaps. Turn this off to receive priced dates only.

## `maxRouteMonths` (type: `integer`):

A safety cap on the size of the run. One route-month is one route and one calendar month. Two routes across three months is six route-months.

## `maxRows` (type: `integer`):

Stop after this many dates have been collected. The run stops searching at that point, so nothing beyond the limit is collected or charged.

## Actor input object example

```json
{
  "origin": "DUB",
  "destination": "STN",
  "routes": [],
  "tripType": "oneWay",
  "monthsAhead": 3,
  "currency": "EUR",
  "includeFlightNumbers": true,
  "includeUnavailableDates": true,
  "maxRouteMonths": 200,
  "maxRows": 20000
}
```

# Actor output Schema

## `fareCalendar` (type: `string`):

One row per route, direction and date.

## `fareCalendarCsv` (type: `string`):

The same rows as a spreadsheet download.

## `runSummary` (type: `string`):

Counts of dates collected, months with nothing on sale, and searches that could not be completed.

## `consoleRun` (type: `string`):

The run in the console, with its log.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "origin": "DUB",
    "destination": "STN",
    "routes": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("apt_marble/ryanair-fare-calendar-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "origin": "DUB",
    "destination": "STN",
    "routes": [],
}

# Run the Actor and wait for it to finish
run = client.actor("apt_marble/ryanair-fare-calendar-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "origin": "DUB",
  "destination": "STN",
  "routes": []
}' |
apify call apt_marble/ryanair-fare-calendar-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,apt_marble/ryanair-fare-calendar-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/xmdIt58vRCfXhqxTN/builds/k1w91fmy6XhJ9rev5/openapi.json
