# EU Maritime & Port Opportunity Monitor (`gabichuuuuu/eu-maritime-port-opportunity-monitor`) Actor

Monitor EU and Spanish port and maritime tenders: dredging, quays, breakwaters, shore power and coastal works. Qualified new tenders, deadline changes, awards and funding calls from official PLACSP, TED and EU Funding & Tenders data, via API or scheduled runs.

- **URL**: https://apify.com/gabichuuuuu/eu-maritime-port-opportunity-monitor.md
- **Developed by:** [Gabriel Spoleto Couto](https://apify.com/gabichuuuuu) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $100.00 / 1,000 qualified opportunities

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

## EU Maritime & Port Opportunity Monitor

Monitor **EU maritime tenders, port procurement, public contracts, and funding signals** through
one API-first Apify Actor. It finds technically relevant opportunities in official Spanish and
European Union data, applies a deterministic maritime taxonomy, and emits traceable lifecycle
changes for CRM, bid/no-bid, market-intelligence, and business-development workflows.

This is procurement intelligence for companies bidding for public contracts. It is not a jobs,
vacancies, candidates or employment scraper.

The maritime procurement monitor is designed for marine contractors, engineering consultancies,
dredging and coastal works specialists, port-equipment suppliers, shore-power providers, terminal
operators, and business-development teams. It returns qualified lifecycle events instead of an
unfiltered dump of public notices.

> **Version 0.2** scans each source incrementally, defers events beyond the run limits instead of
> dropping them, and qualifies opportunities with a stricter maritime classifier. Validate
> representative countries, sources, filters, and budgets before scheduling a production dependency.

> **Start small:** run once without `monitorId` to get a snapshot of every qualified opportunity
> in the lookback window. For a recurring monitor, set a stable `monitorId`: the first successful
> observation can emit `NEW` events; later runs with the same `monitorId` emit only newly qualified
> or materially changed opportunities.

### Maritime tender monitoring use cases

- Feed new port tenders and deadline changes into a CRM or bid pipeline.
- Track dredging, breakwater, quay, berth, terminal, VTS, and port-safety contracts.
- Find shore-power, alternative-fuel, and port-decarbonisation opportunities.
- Monitor pre-tender consultations before formal procurement opens.
- Follow awards and cancellations for competitor and market intelligence.
- Combine Spanish PLACSP notices, EU TED procurement, and Funding & Tenders signals in one dataset.
- Schedule country, region, CPV, budget, and keyword-specific monitors for business-development teams.

### What port and maritime tenders it monitors

- Port, harbour, quay, berth, jetty, breakwater and terminal works.
- Dredging, reclamation, coastal protection and marine construction.
- Mooring, fendering, navigation, VTS/VTMS and port-safety systems.
- Shore-side electricity, alternative fuels and port decarbonisation.
- Maritime engineering, geotechnical and environmental services.

The built-in taxonomy combines maritime CPV roots, deterministic Spanish and English terminology,
buyer/authority signals and user-supplied filters. Every result includes the matched rules; the
Actor does not use an LLM or an opaque win-probability score.

### Official tender sources: PLACSP, TED and EU Funding & Tenders

| Source | Signals | Interface |
|---|---|---|
| PLACSP | Spanish preliminary consultations, tenders, awards and cancellations | Official Atom/XML open data |
| TED | EU procurement notices and awards | Official Search API v3 |
| EU Funding & Tenders | Calls and funded-project signals that may precede procurement | Official public Search API |

Source documentation: [PLACSP open data](https://contrataciondelestado.es/wps/portal/DatosAbiertos),
[TED Search API](https://docs.ted.europa.eu/api/latest/search.html), and
[Funding & Tenders APIs](https://ec.europa.eu/info/funding-tenders/opportunities/portal/screen/support/apis).

### Input

```json
{
  "monitorId": "iberian-port-works",
  "sources": ["placsp", "ted", "funding_tenders"],
  "countries": ["ES", "PT"],
  "regions": [],
  "cpvCodes": ["45241000", "45252124"],
  "keywords": ["dragado", "shore power"],
  "minBudget": 100000,
  "publishedAfter": "2026-07-01",
  "signalTypes": ["PRE_TENDER", "OPEN", "AWARDED", "FUNDED_PROJECT"],
  "maxResults": 100,
  "maxScannedRecords": 500,
  "minRelevanceScore": 0.35
}
```

#### Input fields

| Field | Type | Required | Default | Limits and behavior |
|---|---|---:|---|---|
| `monitorId` | `string` | No | — | Stable state key, 3–80 characters: letters, numbers, `_`, or `-`. Omit it for a one-off snapshot. |
| `sources` | `string[]` | No | all three | One or more of `placsp`, `ted`, `funding_tenders`; maximum 3. |
| `countries` | `string[]` | No | `["ES"]` | ISO alpha-2 codes; 1–30 unique values. `EL` and `UK` are accepted as `GR` and `GB`. |
| `regions` | `string[]` | No | `[]` | Maximum 50. NUTS codes such as `ES51` also match their subregions; region names match exactly. |
| `cpvCodes` | `string[]` | No | `[]` | Maximum 100. A code matches its whole CPV branch: `4524` or `45240000` also match `45241000`. |
| `keywords` | `string[]` | No | `[]` | Extra Spanish or English terms; maximum 100. Any term may match (OR). |
| `minBudget` | `number` | No | — | Minimum published budget. Records without a budget are excluded when set. |
| `publishedAfter` | `string` | No | previous 30 days | ISO date in `YYYY-MM-DD` format for the first scan; later runs continue from the source cursors. |
| `signalTypes` | `string[]` | No | `[]` (all) | Any of `PRE_TENDER`, `OPEN`, `AWARDED`, `CANCELLED`, `FUNDED_PROJECT`, `UNKNOWN`. |
| `maxResults` | `integer` | No | `100` | 1–1,000 events emitted per run, most relevant first; the rest are deferred to the next runs. |
| `maxScannedRecords` | `integer` | No | `500` | 50–5,000 upstream records scanned per source and run, independent of `maxResults`. |
| `minRelevanceScore` | `number` | No | `0.35` | Deterministic classifier threshold from 0 to 1. |
| `requestTimeoutSeconds` | `integer` | No | `30` | 5–120 seconds for each official-source request. |
| `maxConcurrency` | `integer` | No | `3` | 1–3 official sources queried concurrently. |

Apify Console prefills `maxResults` with 20 and `maxScannedRecords` with 100 so that a first run
finishes in about two minutes; API calls without these fields use the defaults above.

`monitorId` is stable state, not a display label. Reusing it compares the new scan with the last
successful observation. Use a different ID for a different commercial profile. Without `monitorId`
each run is an independent snapshot: it reports every qualified opportunity in the lookback window
and remembers nothing for later runs.

Every source applies the same filter semantics. A filter excludes a record only when the record
publishes that field and none of its values match: a notice without a CPV code is kept by a CPV
filter, and the classifier then decides whether it is relevant. Keywords match the start of a word in
the title or description, ignoring case and accents. NUTS codes are sent to TED; region names are
applied locally.

The first run of a monitor scans the `publishedAfter` window (30 days by default). Each later run
continues from where PLACSP and TED stopped, so it reads only new or changed notices; Funding &
Tenders publishes no modification date and is rescanned within the window. Changing any
relevance filter (countries, regions, CPV codes, keywords, budget, `publishedAfter` or the relevance
score) restarts the lookback window. PLACSP publishes a newest-first feed and the Actor reads up to
about three days of it per run, so **schedule monitors at least daily**. When a source reaches
`maxScannedRecords` or its page limit, it reports `PARTIAL` in `RUN_SUMMARY`. Inputs are bounded to
protect upstream services, run costs and storage.

### Output

Each default Dataset item is a traceable change event. The following item is the exact serialized
output produced by the v0.2 transition and event-building runtime for a deterministic TED fixture.
It documents the response contract, not a current live notice or a performance claim; source data
and matched rules can change between runs.

```json
{
  "recordType": "OPPORTUNITY_EVENT",
  "schemaVersion": "2.0",
  "eventType": "NEW",
  "opportunityId": "ted:123456-2026",
  "projectKey": "sha256:1c26485c91b370eb0d15814768250911fbfc04a65b1c41a980e39de4a25a2fec",
  "source": "ted",
  "sourceRecordId": "123456-2026",
  "title": "Shore-side electricity installation at the port",
  "description": null,
  "phase": "OPEN",
  "buyer": "Port Authority",
  "supplier": null,
  "countries": ["ES"],
  "regions": [],
  "cpvCodes": ["45241600"],
  "budgetValue": 3200000.0,
  "budgetCurrency": "EUR",
  "publishedAt": "2026-08-09T10:00:00+00:00",
  "deadlineAt": "2026-10-15T12:00:00+00:00",
  "relevanceScore": 1.0,
  "matchedRules": [
    "category:port_infrastructure",
    "category:marine_energy",
    "keyword:port",
    "keyword:shore-side electricity",
    "cpv:45241600",
    "score:CPV_MATCH",
    "score:TITLE_TERM",
    "score:BUYER_TERM",
    "score:MULTI_SIGNAL"
  ],
  "changedFields": [],
  "sourceUrl": "https://api.ted.europa.eu/v3/notices/123456-2026",
  "noticeUrl": "https://ted.europa.eu/en/notice/-/detail/123456-2026",
  "sourceEvidence": [
    {
      "source": "ted",
      "sourceRecordId": "123456-2026",
      "sourceUrl": "https://api.ted.europa.eu/v3/notices/123456-2026",
      "noticeUrl": "https://ted.europa.eu/en/notice/-/detail/123456-2026"
    }
  ],
  "firstSeenAt": "2026-08-09T10:00:00+00:00",
  "lastSeenAt": "2026-08-09T10:00:00+00:00"
}
```

Supported change events are:

- `NEW`: first qualified observation for this monitor.
- `UPDATED`: one or more material fields changed.
- `DEADLINE_CHANGED`: the submission deadline changed.
- `AWARDED`: the source now reports an award.
- `CANCELLED`: the source explicitly reports cancellation.

Absence from a source response is never interpreted as cancellation. Source health and counts are
written to the default Key-Value Store under `RUN_SUMMARY`; historical state, source cursors and
billing markers live in the named store derived from `monitorId`, or in the run's default store
for a snapshot run. Besides health, errors and warnings, `RUN_SUMMARY` reports `qualifiedCount`
(qualified event groups found), `emittedCount`,
`deferredCount` (groups left for the next run by `maxResults` or the charge limit),
`chargeLimitReached`, `duplicateCount` and `sourceCursors`.

#### Output fields

| Field group | What it contains |
|---|---|
| Identity | `opportunityId`, cross-source `projectKey`, `source`, and `sourceRecordId`. |
| Change | `eventType`, `phase`, `changedFields`, `firstSeenAt`, and `lastSeenAt`. |
| Opportunity | `title`, optional `description`, `buyer`, `supplier`, countries, regions, CPV codes, budget, publication date, and deadline. |
| Qualification | Deterministic `relevanceScore` and the exact `matchedRules`; this is not a probability of winning. |
| Provenance | `sourceUrl`, optional `noticeUrl`, and `sourceEvidence[]` for verification against official data. |

A repeat scan can legitimately return an empty Dataset when no new or materially changed qualified
opportunity is observed. Inspect `RUN_SUMMARY` to distinguish “no changes” from partial or failed
source coverage.

### Pricing

The Actor uses two Pay-Per-Event charges:

- `monitor-scan`: USD 0.50 after at least one official source completes successfully.
- `qualified-opportunity`: USD 0.10 for each persisted `NEW` or changed qualified opportunity.

Platform usage is included in these prices. A run's maximum cost must be at least USD 0.50 so that
the scan itself can be charged.

Duplicates, irrelevant records and failed sources are not charged as qualified opportunities. A
snapshot run without `monitorId` charges every qualified opportunity it reports, because it has no
history to compare with. The
Actor respects the run spending limit and uses idempotency keys so a restart cannot bill the same
event twice. When the limit is reached, the remaining events are deferred to the next run instead of
being dropped. Review the current **Pricing** tab and run a representative sample before scheduling
a production monitor because pricing can change.

### API integrations

The synchronous REST endpoint returns the default Dataset as a JSON array. These examples call the
Actor and process lifecycle events rather than only printing the raw response. Set `APIFY_TOKEN` in
your environment and keep `monitorId` stable across scheduled runs of the same commercial profile.

#### cURL

```bash
set -o pipefail

curl --fail-with-body -X POST \
  "https://api.apify.com/v2/actors/gabichuuuuu~eu-maritime-port-opportunity-monitor/run-sync-get-dataset-items?clean=true" \
  -H "Authorization: Bearer ${APIFY_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "monitorId": "iberian-port-works",
    "countries": ["ES", "PT"],
    "keywords": ["dragado", "shore power"],
    "signalTypes": ["PRE_TENDER", "OPEN", "AWARDED"],
    "maxResults": 100
  }' |
  jq '[.[] | {
    eventType,
    title,
    buyer,
    phase,
    budgetValue,
    deadlineAt,
    relevanceScore,
    noticeUrl
  }]'
```

#### Python

Install the official client with `pip install apify-client`:

```python
import os

from apify_client import ApifyClient


client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("gabichuuuuu/eu-maritime-port-opportunity-monitor").call(
    run_input={
        "monitorId": "iberian-port-works",
        "countries": ["ES", "PT"],
        "keywords": ["dragado", "shore power"],
        "signalTypes": ["PRE_TENDER", "OPEN", "AWARDED"],
        "maxResults": 100,
    }
)

if run is None or run.get("status") != "SUCCEEDED":
    status = None if run is None else run.get("status")
    raise RuntimeError(f"The Actor run did not succeed: {status}")

events = list(client.dataset(run["defaultDatasetId"]).iterate_items())
for event in events:
    crm_record = {
        "external_id": event["opportunityId"],
        "change": event["eventType"],
        "title": event["title"],
        "buyer": event.get("buyer"),
        "deadline": event.get("deadlineAt"),
        "source_url": event.get("noticeUrl") or event["sourceUrl"],
    }
    print(crm_record)

if not events:
    print("No new or materially changed qualified opportunities")
```

#### Node.js

Node.js 18 or newer includes `fetch`. Save this ESM example as `monitor.mjs` and run it with
`node monitor.mjs`:

```javascript
const endpoint = new URL(
  "https://api.apify.com/v2/actors/gabichuuuuu~eu-maritime-port-opportunity-monitor/run-sync-get-dataset-items",
);
endpoint.searchParams.set("clean", "true");

const response = await fetch(endpoint, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.APIFY_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    monitorId: "iberian-port-works",
    countries: ["ES", "PT"],
    keywords: ["dragado", "shore power"],
    signalTypes: ["PRE_TENDER", "OPEN", "AWARDED"],
    maxResults: 100,
  }),
  signal: AbortSignal.timeout(310_000),
});

if (!response.ok) {
  throw new Error(`Apify ${response.status}: ${await response.text()}`);
}

const events = await response.json();
const crmRecords = events.map((event) => ({
  externalId: event.opportunityId,
  change: event.eventType,
  title: event.title,
  buyer: event.buyer,
  deadline: event.deadlineAt,
  sourceUrl: event.noticeUrl ?? event.sourceUrl,
}));

console.log(crmRecords);
```

#### Scheduling and no-change runs

Save the input as an Apify Task and schedule it at least daily. The default Dataset can be consumed
through the Apify API, Make, Zapier or a run webhook. Webhook delivery is an Apify platform feature;
this Actor does not send data to arbitrary user-provided URLs.

### FAQ

#### How do I monitor port and maritime tenders in Spain and the EU?

Run the Actor once without `monitorId` to get a snapshot of every qualified opportunity in the
lookback window. Then save the input as an Apify Task with a stable `monitorId` and schedule it at
least daily: each scheduled run emits only new or materially changed opportunities.

#### Which public procurement sources does it cover?

Spanish public procurement from PLACSP (Plataforma de Contratación del Sector Público), EU
procurement notices and awards from TED (Tenders Electronic Daily), and calls from the EU Funding &
Tenders portal. All three are official, public sources; no login, proxy or paid data is used.

#### Can I get alerts for new tenders, deadline changes, awards and cancellations?

Yes. Every result carries a lifecycle event (`NEW`, `UPDATED`, `DEADLINE_CHANGED`, `AWARDED` or
`CANCELLED`) that you can route to a CRM or bid pipeline through the Apify API, Make, Zapier or a
run webhook.

#### Can I filter by CPV code, country, region, budget or keyword?

Yes. Combine `countries`, `regions`, `cpvCodes`, `keywords`, `minBudget`, `publishedAfter`,
`signalTypes` and `minRelevanceScore`. A change of filters restarts the lookback window for that
monitor.

#### Can I export the tenders to Excel, CSV or JSON?

Yes. Results are stored in the run's Apify Dataset, which you can download as JSON, CSV, Excel or
XML from the Console or read through the Apify API.

#### Does it read tender documents or recommend whether to bid?

No. It qualifies the official notice data and links to the source; it does not interpret PDF tender
documents, give legal advice or estimate a probability of winning.

### Deliberate limits

- No PDF or tender-document interpretation.
- No private, authenticated or paid sources.
- No browser automation or residential proxy.
- No bid recommendation, legal advice or probability of winning.
- No assumption that a missing record was cancelled.
- Published values remain attributable to the official source and should be verified before bidding.

### Development

Runtime: Python 3.13 on `apify/actor-python:3.13`. The code also supports Python 3.12 for local
development.

```powershell
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -e ".[dev]"
.\.venv\Scripts\python.exe -m ruff check src tests scripts
.\.venv\Scripts\python.exe -m mypy src
.\.venv\Scripts\python.exe -m pytest
```

Run the real-source smoke script before a release. Fixtures test parser behavior; the smoke run tests
the assumptions made about the live official services.

```powershell
$env:PYTHONUTF8 = "1"
.\.venv\Scripts\python.exe scripts\smoke_sources.py
```

### Architecture

The code separates source adapters from domain qualification and historical state:

```text
official sources -> normalized source records -> maritime classifier
                 -> cross-source deduplication -> historical diff
                 -> Dataset / RUN_SUMMARY / PPE
```

This separation keeps one upstream schema change from changing the public output contract.

# Changelog

This Actor's version history is a separate document: https://apify.com/gabichuuuuu/eu-maritime-port-opportunity-monitor/changelog.md

# Actor input Schema

## `monitorId` (type: `string`):

Optional stable identifier of a recurring monitor. Reusing it remembers previous runs, so later runs emit only new or changed opportunities. Leave it empty for a one-off snapshot that reports every qualified opportunity in the lookback window.

## `sources` (type: `array`):

Official datasets to query.

## `countries` (type: `array`):

ISO alpha-2 country codes. Spain is selected by default. The EU aliases EL and UK are accepted as GR and GB.

## `regions` (type: `array`):

Optional region filter. NUTS codes such as ES51 also match their subregions (ES511) in every source; region names match exactly and are applied locally. Records that publish no region are kept.

## `cpvCodes` (type: `array`):

Optional CPV filter. A code matches its whole branch in every source: 4524 or 45240000 also match 45241000. Records that publish no CPV are kept.

## `keywords` (type: `array`):

Optional Spanish or English terms. A record matches when any term starts a word of its title or description, ignoring case and accents.

## `minBudget` (type: `number`):

Require a published budget equal to or above this value; records without a budget are excluded.

## `publishedAfter` (type: `string`):

ISO date for the first scan. When omitted, the first scan looks back 30 days (PLACSP reads its most recent pages only). Later runs continue from each source's incremental cursor; changing any filter restarts the lookback.

## `signalTypes` (type: `array`):

Empty means every supported phase.

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

Maximum number of opportunity events emitted in one run, most relevant first. The rest are deferred and emitted by the next runs, never lost.

## `maxScannedRecords` (type: `integer`):

Scan budget per source and run, independent of the emitted results. A source that reaches it reports PARTIAL and the next run continues from its cursor.

## `minRelevanceScore` (type: `number`):

Minimum deterministic maritime relevance score from 0 to 1.

## `requestTimeoutSeconds` (type: `integer`):

Timeout in seconds for each official-source HTTP request.

## `maxConcurrency` (type: `integer`):

Maximum number of official sources queried concurrently.

## Actor input object example

```json
{
  "sources": [
    "placsp",
    "ted",
    "funding_tenders"
  ],
  "countries": [
    "ES"
  ],
  "regions": [],
  "cpvCodes": [],
  "keywords": [],
  "signalTypes": [],
  "maxResults": 20,
  "maxScannedRecords": 100,
  "minRelevanceScore": 0.35,
  "requestTimeoutSeconds": 30,
  "maxConcurrency": 3
}
```

# Actor output Schema

## `opportunities` (type: `string`):

No description

## `runSummary` (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 = {
    "maxResults": 20,
    "maxScannedRecords": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("gabichuuuuu/eu-maritime-port-opportunity-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 = {
    "maxResults": 20,
    "maxScannedRecords": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("gabichuuuuu/eu-maritime-port-opportunity-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 '{
  "maxResults": 20,
  "maxScannedRecords": 100
}' |
apify call gabichuuuuu/eu-maritime-port-opportunity-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,gabichuuuuu/eu-maritime-port-opportunity-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/AX8uXZ87IuJm7Cw4Q/builds/TwKrFukWdDmFz0CQG/openapi.json
