# Spain Train Fares Scraper – Renfe, Avlo & Ouigo Prices (`phantom-x64/spain-train-fares-scraper`) Actor

Extract Renfe (AVE, Avlo, Alvia) and Ouigo train fares in Spain for any date range: departure, arrival, cheapest price, sold-out flag and booking link, plus the cheapest train of each day. Type city names, no station codes. $1 per 1,000 trains.

- **URL**: https://apify.com/phantom-x64/spain-train-fares-scraper.md
- **Developed by:** [Phantom Data](https://apify.com/phantom-x64) (community)
- **Categories:** Travel, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Spain Train Fares Scraper — Renfe, Avlo & Ouigo cheapest prices

Get **every high-speed train between two Spanish cities for a whole date range, with its cheapest fare**, as clean JSON / CSV / Excel:
Renfe (**AVE, Avlo, Alvia, Avant, Intercity**) and **Ouigo**, with departure and arrival time, duration, price, sold-out flag, direct/connection flag and a booking link. On top of that you get **one "cheapest train of the day" row per route and day**, so you can see at a glance which day is cheapest to travel.

- Type **city names** ("Madrid -> Barcelona"), no station codes. 57 Spanish cities built in.
- Up to **60 days** and **20 routes** per run. One click for the return direction.
- Filters: max price, direct trains only, hide sold-out trains.
- No login, no browser: one route × 7 days in about 1-3 minutes.
- **$1 per 1,000 trains.** The "cheapest-only" mode costs $0.002 per route and day.

### What can you use it for?

- **Find the cheapest day to travel** Madrid ⇄ Barcelona, Valencia, Sevilla, Málaga, Alicante...
- **Price tracking / alerts**: schedule the Actor daily and compare prices over time (Avlo and Ouigo prices move a lot).
- **Travel apps, bots and newsletters**: feed a "cheap trains this week" Telegram channel, a comparison site or a spreadsheet.
- **Market research**: compare Renfe, Avlo and Ouigo on price, timetable and availability per corridor.

### Supported operators and cities

| Operator | Trains | Notes |
|---|---|---|
| Renfe | AVE, AVLO, ALVIA, AVANT, Intercity, MD, regional | All Renfe products the booking site returns for the route. Connections are flagged `direct: false`. |
| Ouigo España | OUIGO | Base fare (Essential) for 1 adult, without extras. |
| Iryo | — | Not supported (iryo.eu blocks automated clients). |

**Renfe and Ouigo:** Madrid, Barcelona, Valencia, Alicante, Sevilla, Málaga, Córdoba, Zaragoza, Tarragona, Albacete, Cuenca, Segovia, Valladolid, Murcia, Elche.

**Renfe only:** Lleida, Girona, Figueres, Ciudad Real, Puertollano, Toledo, Guadalajara, Calatayud, Huesca, Castellón, Villena, Orihuela, Granada, Antequera, Jaén, Almería, Cádiz, Jerez, Huelva, Salamanca, Palencia, León, Zamora, Burgos, Ourense, Santiago de Compostela, A Coruña, Vigo, Oviedo, Gijón, Santander, Bilbao, Vitoria-Gasteiz, San Sebastián, Pamplona, Logroño, Badajoz, Mérida, Cáceres, Plasencia.

Cities with several stations (Madrid Atocha + Chamartín, Barcelona Sants + others, Valencia, Zaragoza, Segovia...) are searched as "all stations"; the exact station is in each row. Accents and case don't matter, and common alternatives work (Alacant, Donostia, Coruña, Seville...).

### Quick start (under 1 minute)

1. Click **Try for free** and write routes as city names, for example `Madrid -> Barcelona`. No station codes needed.
2. Choose the first travel date and how many days to search. The pre-filled input is **1 day** so your first run finishes in about 30 seconds; raise *Number of days* for a full price calendar.
3. Click **Start**. Download JSON, CSV or Excel, or schedule it daily to track price changes.

### Input

| Field | Default | What it does |
|---|---|---|
| `routes` | `["Madrid -> Barcelona"]` | One route per line, `Origin -> Destination` (also `Madrid - Barcelona`, `Madrid to Barcelona`). |
| `includeReturn` | `false` | Also search each route in the other direction. |
| `startDate` | today | First travel date, `YYYY-MM-DD`. |
| `days` | `7` | Number of consecutive days (1-60). |
| `endDate` | — | Optional last travel date; replaces `days`. |
| `operators` | `["renfe", "ouigo"]` | Which operators to search. |
| `maxPrice` | — | Only trains at or below this price (EUR). |
| `directOnly` | `false` | Leave out connections. |
| `includeSoldOut` | `true` | Also output sold-out trains (`price: null`). |
| `output` | `trainsAndCheapest` | `trains`, `trainsAndCheapest` or `cheapestOnly`. |
| `maxItems` | `2000` | Cost cap: stop after this many rows. |
| `proxyConfiguration` | no proxy | Optional Apify Proxy. |

Example:

```json
{
  "routes": ["Madrid -> Barcelona", "Madrid -> Valencia"],
  "includeReturn": true,
  "startDate": "2026-12-18",
  "days": 10,
  "maxPrice": 40,
  "output": "trainsAndCheapest"
}
```

### Output

One row per train and day (`recordType: "train"`):

```json
{
  "recordType": "train",
  "route": "Madrid → Barcelona",
  "origin": "Madrid",
  "destination": "Barcelona",
  "date": "2026-11-04",
  "weekday": "Wed",
  "operator": "Ouigo",
  "product": "OUIGO",
  "trainNumber": "06471",
  "departureTime": "06:22",
  "arrivalTime": "09:45",
  "durationMinutes": 203,
  "departureStation": "Madrid - Puerta de Atocha - Almudena Grandes",
  "arrivalStation": "Barcelona - Sants",
  "price": 25.0,
  "currency": "EUR",
  "soldOut": false,
  "direct": true,
  "seatsLeft": null,
  "bookingUrl": "https://ventas.ouigo.com/es-ES/_deeplink/journey-search?origin=MT1&destination=7171801&outboundDate=2026-11-04&...",
  "scrapedAt": "2026-11-02T23:06:19+01:00"
}
```

And one row per route and day with the cheapest train (`recordType: "cheapestPerDay"`; counts refer to the trains that passed your filters - this example used `maxPrice: 40`):

```json
{
  "recordType": "cheapestPerDay",
  "route": "Valencia → Madrid",
  "date": "2026-12-05",
  "weekday": "Sat",
  "cheapestPrice": 17.0,
  "currency": "EUR",
  "cheapestOperator": "Ouigo",
  "cheapestProduct": "OUIGO",
  "cheapestTrainNumber": "06603",
  "cheapestDepartureTime": "20:57",
  "cheapestArrivalTime": "22:53",
  "cheapestBookingUrl": "https://ventas.ouigo.com/...",
  "minPriceByOperator": { "Ouigo": 17.0, "Renfe": 19.0 },
  "minPriceByProduct": { "OUIGO": 17.0, "AVLO": 19.0, "AVE": 24.4 },
  "trainsFound": 7,
  "trainsWithSeats": 7,
  "operatorsSearched": ["Ouigo", "Renfe"]
}
```

The dataset has two views in the Console: **Trains** and **Cheapest per day**. A short `SUMMARY` record (cheapest price per day and per route, and the status of each operator) is also saved in the run's key-value store.

**Prices** are the cheapest fare on sale for **1 adult, one way**, in euros, at the moment of the run: Renfe's lowest fare for that train (e.g. Básico / Avlo base fare) and Ouigo's Essential fare without luggage extras. Prices change constantly; always confirm on the booking page.

### Pricing

Pay per event, no monthly fee:

| Event | Price |
|---|---|
| Train fare row | $0.001 |
| Cheapest-of-the-day row | $0.002 |

Examples: Madrid → Barcelona, 7 days, both operators ≈ 170-210 trains + 7 daily rows ≈ **$0.18-0.23**. The same in `cheapestOnly` mode: **$0.014**. Daily tracking of 5 routes × 30 days in `cheapestOnly` mode: $0.30 per run.

Rows removed by your filters are not charged. `maxItems` and Apify's "maximum cost per run" are respected.

### FAQ

**How far ahead can I search?** Up to 60 days per run. Renfe and Ouigo usually open sales a few months ahead; days that are not on sale yet simply return no trains.

**How long does a run take?** Ouigo is fast (2-3 s per route and day). Renfe answers cloud servers slowly, about 20-25 s per route and day, one request at a time (we keep the load on Renfe polite). Rough guide: 1 route × 7 days ≈ 3 min; 5 routes × 30 days ≈ 1 hour, so raise the run timeout for big searches, or split them into several runs.

**Why is a train shown with `price: null`?** It is sold out or has no fare on sale (`soldOut: true`). Turn off *Include sold-out trains* to hide them.

**Is Iryo included?** No. Iryo blocks automated requests and we don't work around that.

**Renfe dates after 24 January 2027?** Renfe sells trips from 25 January 2027 only on its new booking system, which this Actor does not read yet; those dates return Ouigo trains only.

**Does it buy or reserve tickets?** No. It only reads public prices, like an anonymous visitor of each operator's website. Use the booking link to buy.

**It returned an error for Renfe/Ouigo.** If an operator refuses the connection (HTTP 403 or a virtual waiting room), the run continues with the other operator and the reason is shown in the status message and in `SUMMARY`. Try again later or enable Apify Proxy (residential, Spain) in the *Proxy* field.

### Limits

- Renfe answers cloud servers slowly: expect about 20-25 seconds per route and day. Large searches (5 routes x 30 days) can take about an hour, so raise the run timeout or split them.
- Iryo is not supported (it blocks automated clients).
- Renfe dates after 24 January 2027 move to a new booking system that is not read yet; those dates return Ouigo trains only.
- Prices are public fares for 1 adult without extras. The Actor does not book or reserve tickets.

### Legal note

The Actor reads public fares that Renfe and Ouigo show to any anonymous visitor of their websites. It collects **public data only**: no accounts, no personal data, no purchases. You are responsible for using the data in line with the operators' terms.

### Use it from code (API)

Every Apify Actor is also an API. Replace `<YOUR_API_TOKEN>` with the token from your Apify account (Settings, API & Integrations).

**JavaScript**

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });
const run = await client.actor('phantom-x64/spain-train-fares-scraper').call({
    "routes": [
      "Madrid -> Barcelona"
    ],
    "days": 3
  });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

**Python**

```python
from apify_client import ApifyClient

client = ApifyClient('<YOUR_API_TOKEN>')
run = client.actor('phantom-x64/spain-train-fares-scraper').call(run_input={
      "routes": [
        "Madrid -> Barcelona"
      ],
      "days": 3
    })
items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

You can also start it from Make, Zapier, n8n or Google Sheets with the Apify integrations, schedule it, or call it as a tool from an AI agent through the Apify MCP server.

### Support and feedback

Found a bug, need a new field or another source? Open an issue in the **Issues** tab of this Actor page. Reports with a run link are fixed fastest. If it saves you time, a short review on the Store page helps other people find it.

### Keywords

Renfe API, Renfe scraper, Avlo prices, AVE prices, Ouigo España prices, cheap train tickets Spain, billetes de tren baratos, precios Renfe, Spain rail fares API, train price tracker, Madrid Barcelona train price, cheapest train Spain.

# Actor input Schema

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

One route per line, written as 'Origin -> Destination' with city names, e.g. 'Madrid -> Barcelona', 'Valencia -> Madrid', 'Sevilla -> Madrid'. Accents and case do not matter. Supported cities: Madrid, Barcelona, Valencia, Alicante, Sevilla, Málaga, Córdoba, Zaragoza, Tarragona, Albacete, Cuenca, Segovia, Valladolid, Murcia, Elche (Renfe + Ouigo) and Lleida, Girona, Figueres, Ciudad Real, Puertollano, Toledo, Guadalajara, Calatayud, Huesca, Castellón, Villena, Orihuela, Granada, Antequera, Jaén, Almería, Cádiz, Jerez, Huelva, Salamanca, Palencia, León, Zamora, Burgos, Ourense, Santiago, A Coruña, Vigo, Oviedo, Gijón, Santander, Bilbao, Vitoria, San Sebastián, Pamplona, Logroño, Badajoz, Mérida, Cáceres, Plasencia (Renfe only). Cities with several stations (Madrid, Barcelona, Valencia, Zaragoza...) search all of them. Max 20 routes per run.

## `includeReturn` (type: `boolean`):

Also search every route the other way round (Barcelona -> Madrid for 'Madrid -> Barcelona'), for the same dates.

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

First departure date to search, format YYYY-MM-DD (Spanish time). Leave empty to start today. Past dates are moved to today.

## `days` (type: `integer`):

How many consecutive travel days to search, starting at the first travel date (1-60). Each extra day adds about 5-25 seconds per route (Renfe answers slowly to cloud servers); the default of 7 days takes about 3 minutes (the pre-filled 1 day takes about 30 seconds).

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

Optional last departure date, format YYYY-MM-DD. If set, it replaces 'Number of days'. The range is capped at 60 days.

## `operators` (type: `array`):

Which operators to search. Renfe includes AVE, Avlo (Renfe's low-cost brand), Alvia, Avant, Intercity and regional trains. Ouigo is the low-cost French operator (Madrid, Barcelona, Valencia, Alicante, Andalusia, Murcia, Valladolid/Segovia lines). Iryo is not supported.

## `maxPrice` (type: `integer`):

Keep only trains whose cheapest fare is at or below this price in euros (1 adult). Sold-out trains are then left out. Leave empty for no price limit.

## `directOnly` (type: `boolean`):

Leave out connections with a change of train (Renfe sometimes offers e.g. Avant + AVE combinations).

## `includeSoldOut` (type: `boolean`):

Also output trains with no fare on sale (soldOut = true, price = null). Ignored when a max price is set.

## `output` (type: `string`):

<b>trainsAndCheapest</b>: one row per train and day plus one 'cheapest of the day' row per route and day. <b>trains</b>: only the train rows. <b>cheapestOnly</b>: only one row per route and day with the cheapest train (fewest results, lowest cost).

## `maxItems` (type: `integer`):

Stop after saving (and paying for) this many rows in the whole run, counting train rows and cheapest-per-day rows.

## `proxyConfiguration` (type: `object`):

Optional. By default no proxy is used. If Renfe or Ouigo start refusing requests from Apify servers, select Apify Proxy (residential, country Spain) here.

## Actor input object example

```json
{
  "routes": [
    "Madrid -> Barcelona"
  ],
  "includeReturn": false,
  "days": 1,
  "operators": [
    "renfe",
    "ouigo"
  ],
  "directOnly": false,
  "includeSoldOut": true,
  "output": "trainsAndCheapest",
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# 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 = {
    "routes": [
        "Madrid -> Barcelona"
    ],
    "days": 1,
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("phantom-x64/spain-train-fares-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 = {
    "routes": ["Madrid -> Barcelona"],
    "days": 1,
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("phantom-x64/spain-train-fares-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 '{
  "routes": [
    "Madrid -> Barcelona"
  ],
  "days": 1,
  "maxItems": 100
}' |
apify call phantom-x64/spain-train-fares-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,phantom-x64/spain-train-fares-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/huaIcLU7D7agMiAHp/builds/edAsgs9LhUyW9gDfq/openapi.json
