--- name: evergreen-sailing-schedules-scraper description: Read Evergreen Line point-to-point ocean sailing schedules as structured records via the Apify Actor arman-bd/evergreen-sailing-schedules-scraper. Returns one record per sailing with service loop, port cut-off, ETD/ETA, transit days, vessel and voyage, and VGM cut-offs, for a lane written as ordinary port names (Evergreen's own internal location codes are resolved automatically). Use for rate quoting, loop/alliance tracking, multi-carrier schedule comparison, cut-off reminders or transit-time benchmarking. Not for booking, rates/pricing, container tracking, or the full leg-by-leg transshipment routing detail. --- # Evergreen Sailing Schedules Scraper Apify Actor `arman-bd/evergreen-sailing-schedules-scraper`. Name one or more lanes as `"Origin > Destination"` port names, get one dataset record per sailing found in the window. Runs without credentials, and with no input at all returns Shanghai to Rotterdam sailings for the next 4 weeks. ## When to use it - Quoting a lane: cut-off, transit time and the next few sailings before you talk to a customer. - Tracking which loop (`service`) and which vessel/voyage are running a lane this month, and how that changes over weeks. - Building a multi-carrier schedule comparison, Evergreen is one of the few top-10 carriers still reachable without a browser. - Reminders on `portCutOffDate` or `vgmEdiCutOff` for lanes booked regularly. - Comparing `transitDays` across loops on the same origin-destination pair. ## When not to use it - Booking, rating, or anything that needs an Evergreen account. This Actor only reads published schedules. - Container or bill-of-lading tracking. That is a different Evergreen/ShipmentLink page with a different shape. - The full leg-by-leg transshipment routing (every intermediate port and its own ETD/ETA). This Actor returns the origin-to-destination summary and the `routingDetailSeq` reference; it does not walk the routing-detail sub-page. - A lane Evergreen does not book, or a location name its own search cannot match, both come back as a recorded failure or an empty result, not a workaround. ## Call it ```js import { ApifyClient } from 'apify-client'; const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); const run = await client.actor('arman-bd/evergreen-sailing-schedules-scraper').call({ routes: ['Shanghai > Rotterdam', 'Kaohsiung > Los Angeles'], departureDate: '', durationWeeks: 4, sortBy: 'LINE', sortOrder: 'ASC', reeferCargo: false, maxSailingsPerRoute: 50, }); const { items } = await client.dataset(run.defaultDatasetId).listItems(); const { value: summary } = await client .keyValueStore(run.defaultKeyValueStoreId) .getRecord('RUN_SUMMARY'); ``` One-shot over HTTP, when you want the rows back in the same request: ```bash curl -X POST "https://api.apify.com/v2/acts/arman-bd~evergreen-sailing-schedules-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"routes":["Shanghai > Rotterdam"],"durationWeeks":4}' ``` The Actor is also exposed through Apify's MCP server as `arman-bd/evergreen-sailing-schedules-scraper`, so an MCP-capable agent can call it with no extra wiring. ## Input | Field | Type | Required | Default | Notes | |---|---|---|---|---| | `routes` | string[] | no | `["Shanghai > Rotterdam"]` | One lane per entry, `"Origin > Destination"` as port/city names, or a 5-character Evergreen code directly. An entry with no `>` separator is dropped and listed in `RUN_SUMMARY.invalidRoutes`. | | `departureDate` | string | no | today | `YYYY-MM-DD`. Empty means today on the Actor's server clock. | | `durationWeeks` | integer | no | `4` | Search window ahead of `departureDate`. Clamped to 1-12, this endpoint has no separate pagination, so it is the only depth control. | | `sortBy` | string | no | `"LINE"` | One of `LINE`, `TRANSDAY`, `POLDEP`, `PODARR`. | | `sortOrder` | string | no | `"ASC"` | `ASC` or `DESC`. | | `reeferCargo` | boolean | no | `false` | Restrict to sailings carrying reefer cargo on this lane. | | `maxSailingsPerRoute` | integer | no | `50` | Cap on sailings saved and charged per route. Clamped to 1-200. | **Location names are resolved through Evergreen's own location search**, not treated as UN/LOCODEs, Evergreen's internal codes differ from UN/LOCODEs for a number of ports (e.g. Rotterdam is `NLRDM`, not `NLRTM`; Kaohsiung is `TWKSG`, not `TWKHH`). The search's own best match is used; a more ambiguous name (a US town sharing a European port's name) may need a state/country qualifier or the direct 5-character code to land on the right one. ## Output One record per sailing. | Field | Type | Notes | |---|---|---| | `routeRequested` | string | The route exactly as given, `"Origin > Destination"`. | | `originCode`, `destinationCode` | string | Evergreen's 5-character location codes. | | `originLabel`, `destinationLabel` | string | The name used for the query, upper-cased. | | `recordNo` | number | Row number in Evergreen's own results table. Not a stable ID across runs, a lane's row order can shift as sailings move in and out of the window. | | `placeOfReceipt`, `portOfLoading` | string | Cargo receipt point and load port. | | `portCutOffDate` | string | Port cut-off, Evergreen's own display format. | | `service` | string | Loop/service code, e.g. `FAL3`. | | `portOfDischarge`, `placeOfDelivery` | string | Discharge port and delivery point. | | `transitDays` | number | Total transit time in days. | | `departureDate`, `arrivalDate` | string | ETD at load port, ETA at discharge port, Evergreen's own display format. | | `vesselVoyage` | string | Vessel name and voyage number. Often a partner-line vessel sailing on an Evergreen-named loop, that is expected, not an error. | | `vgmHardCopyCutOff`, `vgmEdiCutOff` | string \| null | VGM cut-off by hard copy and by EDI/WEB/APP. `null` where Evergreen has not published it yet. | | `routingDetailSeq` | number \| null | Sequence ID Evergreen uses for this sailing's routing-detail sub-page. | | `remark` | string \| null | Any remark Evergreen attaches. | | `searchedDepartureDate`, `durationWeeks` | string, number | The window this record was found in. | | `scrapedAt` | string | Run timestamp, ISO 8601. | A real record: ```json { "routeRequested": "Shanghai > Rotterdam", "originCode": "CNSHG", "destinationCode": "NLRDM", "originLabel": "SHANGHAI", "destinationLabel": "ROTTERDAM", "recordNo": 1, "placeOfReceipt": "SHANGHAI", "portOfLoading": "SHANGHAI", "portCutOffDate": "AUG-19-2026 12:00", "service": "FAL3", "portOfDischarge": "ROTTERDAM", "placeOfDelivery": "ROTTERDAM", "transitDays": 36, "departureDate": "AUG-22-2026", "arrivalDate": "SEP-27-2026", "vesselVoyage": "CMA CGM JACQUES SAADE 0FMMQW1MA", "vgmHardCopyCutOff": null, "vgmEdiCutOff": "AUG-18-2026 17:00", "routingDetailSeq": 21, "remark": null, "searchedDepartureDate": "2026-08-22", "durationWeeks": 4, "scrapedAt": "2026-08-22T09:14:03.187Z" } ``` ## RUN_SUMMARY Written to the run's key-value store under `RUN_SUMMARY`. **Read it.** ```json { "routesRequested": ["Shanghai > Rotterdam", "Kaohsiung > Los Angeles"], "invalidRoutes": [], "routesFailed": 1, "failures": [ { "route": "Kaohsiung > Los Angeles", "stage": "fetch-schedule", "error": "retryable HTTP 503" } ], "parseWarnings": [], "sailingsSaved": 4, "filters": { "departureDate": "2026-08-22", "durationWeeks": 4, "sortBy": "LINE", "sortOrder": "ASC", "reeferCargo": false, "maxSailingsPerRoute": 50 }, "finishedAt": "2026-08-22T09:14:05.912Z" } ``` `failures[].stage` is one of `resolve-location` (the port name did not match anything in Evergreen's own search), `fetch-schedule` (the query itself failed after retries), or `parse-schedule` (the page came back but its row shape did not match what the Actor expects, worth reporting, since it usually means the page changed). `parseWarnings` holds individual rows that did not parse cleanly even when the rest of the route succeeded. `invalidRoutes` holds `routes` entries with no `>` separator at all, caught before any request was made. ## Behaviour to plan around - **No pagination on this endpoint.** `durationWeeks` (1-12) is the only depth control; every sailing in that window comes back in a single page. `maxSailingsPerRoute` bounds what is kept and charged for, independently. - **An empty result is not a failure.** A self-loop, or a lane Evergreen simply doesn't run, answers "no sailings" for that route, zero records, nothing in `failures`. Only a route that could not be resolved or fetched lands there. - **Location names go through Evergreen's own search, first match wins.** A common city name shared by several small towns (several US towns named "Hamburg", for instance) resolves to whichever one Evergreen's search ranks first, usually the major port, but check `originLabel`/`originCode` on the first run against a new name before relying on it. - **A 5-character token is used as a code directly, no lookup.** If you already know Evergreen's code for a location, write it in place of a name and the Actor skips the search entirely. - **`recordNo` is positional, not a stable key.** It is Evergreen's own row number for that specific query; it can shift between runs as sailings enter or leave the window. Key on `originCode` + `destinationCode` + `vesselVoyage` + `departureDate` if you need a stable identity for a sailing across runs. - **Dates stay in Evergreen's own display format**, not converted to ISO, parse them yourself if you need to sort or compare programmatically. - **The same lane listed twice, in any case, is charged once.** - **Transient errors are retried** three times with linear backoff, separately for the location lookup and the schedule fetch. A route that fails every attempt is recorded and the run continues with the rest. ## Recipes **Quote a lane right now.** ```json { "routes": ["Shanghai > Rotterdam"], "durationWeeks": 4, "sortBy": "POLDEP", "sortOrder": "ASC" } ``` Take the first record by `departureDate` for the soonest sailing still open. **Compare loops on one lane by speed.** ```json { "routes": ["Shanghai > Rotterdam"], "durationWeeks": 8, "sortBy": "TRANSDAY", "sortOrder": "ASC" } ``` Group by `service` and take the minimum `transitDays` per loop. **Watch several lanes for cut-off reminders.** ```json { "routes": ["Shanghai > Rotterdam", "Kaohsiung > Los Angeles", "Hamburg > New York"], "durationWeeks": 4, "maxSailingsPerRoute": 10 } ``` Run daily and alert when `portCutOffDate` or `vgmEdiCutOff` falls inside your booking lead time.