# Barcelona Tenerife Cruise Port Schedule Scraper (Port Calls) (`steadfetch/puerto-tenerife-cruceros-monitor`) Actor

Public port calls from two Spanish authorities as JSON: today's Barcelona calls via Portic (cruises, ferries, cargo - filter shipType) plus the forward Tenerife cruise schedule (La Palma, La Gomera, El Hierro too). Vessel, IMO, line, berth, ISO times. monitor mode emits only new or changed calls.

- **URL**: https://apify.com/steadfetch/puerto-tenerife-cruceros-monitor.md
- **Developed by:** [Steadfetch](https://apify.com/steadfetch) (community)
- **Categories:** Travel, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 cruise calling delivereds

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

This Actor returns public port calls from Spanish port authorities as one validated dataset item per
calling. It currently covers the future cruise schedule of the
[Autoridad Portuaria de Santa Cruz de Tenerife](https://www.puertosdetenerife.org/cruceros-previstos/)
(Tenerife, La Palma, La Gomera and El Hierro) and today's public port calls of the Port of Barcelona
published by Portic.

**Scope note:** the Port of Barcelona publishes all port traffic, so Barcelona items include cruise
ships, ferries, yachts and cargo vessels, not only cruises. The Actor returns every call the source
publishes; filter the dataset by `shipType`, `ship` or `line` when you only need passenger ships.

### What it does

The Actor reads the public schedules and delivers one item per call with:

- **Common multiport fields**: `authority` (`tenerife` or `barcelona`), `source`, `ship`, `berth`,
  `arrivalDate`/`arrivalTime`, `departureDate`/`departureTime`, and `arrivalAt`/`departureAt` as
  ISO 8601 instants with the port's UTC offset (`Atlantic/Canary` or `Europe/Madrid`, DST-aware).
- **Source-specific fields**: Tenerife adds `agent`, `passengerCapacity`, `crewCapacity`, `port`
  (port of call) and `homePort`; Barcelona adds `shipType`, `imo`, `line` (operator) and `agent`
  (consignee company). Fields a source does not publish are `null`; the Actor never invents a value.
- **Monitor mode**: `monitorMode` remembers every delivered calling in a named key-value store, so a
  daily run only returns new or changed callings and only charges for those.

Honest limits: HTTP only, no browser, no login, no paywall, no CAPTCHA bypass and no proxies. The
Barcelona public list only exposes **today's** calls, so a `date` other than today returns no
Barcelona rows; Tenerife publishes a forward schedule. `url`, `sourceUrl` and `scrapedAt` give the
provenance of every row. No personal data is emitted: `agent` is always a company, and there are no
contact, passenger or crew-name fields.

### How to use

1. Start a run from the Apify Console. The Store prefill reads today's Barcelona calls
   (`ports: ["barcelona"]`, `monitorMode` off, `maxResults` 10).
2. For ongoing monitoring, set `monitorMode` to `true` so each scheduled run bills only new or
   changed callings.
3. Or call it from the CLI:

```bash
apify call steadfetch/puerto-tenerife-cruceros-monitor --input='{"ports":["tenerife","barcelona"],"maxResults":100,"monitorMode":true}'
```

4. Read the dataset: one item per calling, validated against the dataset schema.

### Input

#### Input fields

| Field | Type | Required | Default/Prefill | Description | Example |
| --- | --- | --- | --- | --- | --- |
| `ports` | array of `tenerife` / `barcelona` | No | default `[]` (all active sources), prefill `["barcelona"]` | Port authorities to read. Empty or omitted reads every active source. | `["tenerife","barcelona"]` |
| `date` | string (YYYY-MM-DD) | No | defaults to today (UTC) | Keep only callings that arrive on this exact date. Barcelona publishes only today's calls. | `2026-10-24` |
| `ship` | string | No | - | Optional case-insensitive filter on the vessel name. | `EXCELSIOR` |
| `line` | string | No | - | Optional case-insensitive filter on the operator/line (armador). Tenerife rows have no line, so they are excluded when set. | `GRIMALDI` |
| `since` | string (YYYY-MM-DD) | No | prefill `2026-10-21`; defaults to today (UTC) | Legacy window: keep callings whose arrival date is on or after this date. Ignored when `date` is set. | `2026-10-21` |
| `maxResults` | integer (1-5000) | No | default `100`, prefill `10` | Maximum number of new callings to push after filtering and monitor dedupe. | `10` |
| `monitorMode` | boolean | No | default `true`, prefill `false` | When enabled, callings already delivered by a previous run are not pushed or charged. Disable for a full backfill. | `false` |
| `strictSchema` | boolean | No | default `true` | Fail the run when an item does not match the dataset schema. Keep enabled. | `true` |

#### Example input

```json
{
  "ports": ["barcelona"],
  "maxResults": 10,
  "monitorMode": false,
  "strictSchema": true
}
```

### Output

#### Example output

One Barcelona row with the common fields, built from the dataset schema field examples:

```json
{
  "url": "https://app.portic.net/WebPortic2005/escalas.do",
  "sourceUrl": "https://app.portic.net/WebPortic2005/escalas.do",
  "authority": "barcelona",
  "source": "portic",
  "ship": "EXCELSIOR",
  "vessel": "EXCELSIOR",
  "shipType": "Transbordadores",
  "imo": "9184419",
  "line": "GRANDI NAVI VELOCI",
  "title": "EXCELSIOR - Barcelona - 2026-10-24",
  "arrivalDate": "2026-10-24",
  "arrivalTime": "23:00",
  "departureDate": "2026-10-25",
  "departureTime": "01:30",
  "arrivalAt": "2026-10-24T23:00:00+02:00",
  "departureAt": "2026-10-25T01:30:00+02:00",
  "agent": "TRANSPORTES Y CONSIGNACIONES MARITIMAS",
  "passengerCapacity": null,
  "crewCapacity": null,
  "port": "Barcelona",
  "berth": "18C",
  "homePort": null,
  "scrapedAt": "2026-10-24T21:00:00.000Z"
}
```

A Tenerife row keeps its original contract and adds the common fields (`authority`, `source`,
`ship`, `arrivalAt`, `departureAt`).

#### Output fields

| Field | Type | Description |
| --- | --- | --- |
| `url` | string (uri) | Public schedule page the calling was read from. |
| `sourceUrl` | string (uri) | Same schedule endpoint, kept as an explicit provenance field. |
| `authority` | `tenerife` or `barcelona` | Port authority that publishes the schedule. |
| `source` | string | Short identifier of the public endpoint (`puertosdetenerife` or `portic`). |
| `ship` | string | Vessel name (common multiport field). |
| `vessel` | string | Vessel name; kept for the original Tenerife contract. |
| `shipType` | string or null | Vessel type as published (e.g. Transbordadores, Pasaje, Portacontenedores). |
| `imo` | string or null | IMO / Lloyd's register number when published. |
| `line` | string or null | Shipping line or operator (armador) when published. |
| `title` | string or null | Human-readable label built from vessel, port and arrival date. |
| `arrivalDate` | string (date) | Arrival date in ISO 8601 (YYYY-MM-DD). |
| `arrivalTime` | string (HH:MM) | Arrival time, local port time. |
| `departureDate` | string (date) | Departure date in ISO 8601 (YYYY-MM-DD). |
| `departureTime` | string (HH:MM) | Departure time, local port time. |
| `arrivalAt` | string (date-time) | Arrival instant with the port's UTC offset. |
| `departureAt` | string (date-time) | Departure instant with the port's UTC offset. |
| `agent` | string or null | Consignee / shipping agency company; never a private individual. |
| `passengerCapacity` | integer or null | Published passenger capacity, null when blank. |
| `crewCapacity` | integer or null | Published crew capacity, null when blank. |
| `port` | string | Port of the call (Tenerife) or `Barcelona` (Portic). |
| `berth` | string or null | Berth or dock assigned in the schedule, null when blank. |
| `homePort` | boolean or null | Home-port (turnaround) flag; null when the source does not publish it. |
| `scrapedAt` | string (date-time) | ISO 8601 UTC timestamp of the scrape. |

### Pricing

Pay-per-event, the two synthetic events declared in `.actor/pay_per_event.json`:

- `apify-actor-start`: 0.00005 USD per run. The Apify-recommended default, which includes the first
  5 seconds of compute per run for free.
- `apify-default-dataset-item`: 0.0025 USD per new calling = **2.50 USD per 1,000 callings**. Cost
  per 1,000 callings: both sources are public HTML, parsing takes a measured ~10 ms per page in local
  runs, and a full run issues 1 HTTP request for Tenerife and 2 (search + list) for Barcelona, so the
  measured cost per 1,000 callings is < 0.01 USD and the margin is above 99%.

There is no custom per-item event, so a result is never billed twice. Duplicated, filtered or
skipped rows are not delivered and not billed; items that fail schema validation and runs that
return nothing are not billed as dataset items.

**Works on the Apify free plan** - about 1,999 callings for the $5 monthly credit ((5.00 USD - 0.0002 USD run-start at the default 4 GB memory) / 0.0025 USD per calling).

### Other Steadfetch Actors

- [Exceltur Tourism Indicators Extractor](https://apify.com/steadfetch/exceltur-indicadores-extractor) -
  Spanish tourism GDP and impact indicators to put cruise arrivals in market context.
- [Recreation.gov Campground Extractor](https://apify.com/steadfetch/recreation-gov-campground-extractor) -
  accommodation supply records for benchmarking destination capacity.
- [NYC Restaurant Inspection Scores](https://apify.com/steadfetch/nyc-restaurant-inspection-scores) -
  port-of-call hospitality quality signals for cruise itinerary planning.

### FAQ

**Does it need a login, an API key or a proxy?**
No. Both sources are public HTML pages; the Actor uses plain HTTP with no browser and no proxies.

**Why is `line`, `shipType` or `homePort` null?**
The source does not publish it. Tenerife rows carry the home-port flag but no line or ship type;
Barcelona rows carry line and ship type but no capacities or home-port flag. The Actor reports null
instead of inventing a value.

**Why do Barcelona rows only cover today?**
The public Portic list exposes only the current day's calls (`escalasHoy`). For a future window use
the Tenerife source, which publishes the upcoming schedule.

**Why does a daily run return few or zero items?**
With `monitorMode` enabled (the schema default), only callings not seen by a previous run are
delivered and billed. Disable it for a full backfill.

**Does it include passenger names or crew lists?**
No. The schema holds vessel- and company-level fields only; there are no contact or person fields.

**Does it only return cruise ships?**
No. Barcelona's Portic source publishes every vessel call in the port (cruise ships, ferries,
yachts and cargo) and all of them are returned. Tenerife publishes its cruise schedule. Filter the
dataset by `shipType` if you only need passenger calls.

**Where does the data come from?**
The official public schedules of the Autoridad Portuaria de Santa Cruz de Tenerife and the Port of
Barcelona (Portic), cited in every item via `url` and `sourceUrl`.

### Support

Report bugs through the Actor's Apify Store page (Issues tab) or the steadfetch actors repository
issues. Please include the run ID and the exact input you used.

### Maintenance

Tested daily: daily catalog QA runs the default input (Barcelona, Portic); the default input switched to Barcelona on 2026-10-24 (Tenerife source tested since 2026-10-21).
`npm test`, `npm run publish:check` and `apify-cli validate-schema` run in CI on every push.

# Actor input Schema

## `ports` (type: `array`):

Port authorities to read: 'tenerife' (future cruise schedule of the Autoridad Portuaria de Santa Cruz de Tenerife) and/or 'barcelona' (today's public port calls from Portic). Empty or omitted reads every active source.

## `date` (type: `string`):

Keep only callings that arrive on this exact ISO date. Defaults to today (UTC). Barcelona publishes only today's calls, so a different date returns no Barcelona rows.

## `ship` (type: `string`):

Optional case-insensitive filter on the vessel name, e.g. 'EXCELSIOR'.

## `line` (type: `string`):

Optional case-insensitive filter on the shipping line / operator (armador). Tenerife items carry no line, so they are excluded when this filter is set.

## `since` (type: `string`):

Legacy window: keep callings whose arrival date is on or after this ISO date. Ignored when 'date' is set. Defaults to today (UTC).

## `maxResults` (type: `integer`):

Maximum number of new callings to push to the dataset after filtering and monitor dedupe. Default 100 keeps the first run quick and cheap on the Apify free plan; raise it to pull the full schedule (maximum 5,000).

## `monitorMode` (type: `boolean`):

When enabled, callings already delivered by a previous run are not pushed or charged. Disabled by default in the Store so the first run is a deterministic backfill; enable it to receive only new or changed callings.

## `strictSchema` (type: `boolean`):

Fail the run when an item does not match the dataset schema. Keep enabled: Steadfetch never publishes Actors without output-schema validation.

## Actor input object example

```json
{
  "ports": [
    "barcelona"
  ],
  "since": "2026-10-21",
  "maxResults": 10,
  "monitorMode": true,
  "strictSchema": true
}
```

# Actor output Schema

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

Dataset items validated against the item schema in dataset_schema.json (fields).

# 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 = {
    "ports": [
        "barcelona"
    ],
    "since": "2026-10-21",
    "maxResults": 10,
    "monitorMode": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadfetch/puerto-tenerife-cruceros-monitor").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 = {
    "ports": ["barcelona"],
    "since": "2026-10-21",
    "maxResults": 10,
    "monitorMode": False,
}

# Run the Actor and wait for it to finish
run = client.actor("steadfetch/puerto-tenerife-cruceros-monitor").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 '{
  "ports": [
    "barcelona"
  ],
  "since": "2026-10-21",
  "maxResults": 10,
  "monitorMode": false
}' |
apify call steadfetch/puerto-tenerife-cruceros-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,steadfetch/puerto-tenerife-cruceros-monitor"
        }
    }
}
```

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/7hSmWhHMIlmV7FESb/builds/zOgLhJ0G6y7mAa93w/openapi.json
