Agent skill

scandinavia-transit

Search trains, buses, and ferries across Norway (Entur), Sweden (ResRobot), and Denmark (Rejseplanen) for intra-Scandinavia travel planning. Use when planning ground transport between cities within Scandinavia. Triggers on "train", "bus", "ferry", "Vy", "SJ", "DSB", "Oslo to Bergen", "Stockholm to", "Copenhagen to", "Entur", "ResRobot", "Rejseplanen", "how to get between", "ground transport", "rail", or any intra-Scandinavia routing question.

Stars 387
Forks 22

Install this agent skill to your Project

npx add-skill https://github.com/borski/travel-hacking-toolkit/tree/main/skills/scandinavia-transit

SKILL.md

Scandinavia Transit Skill

Search ground transport (trains, buses, ferries) within Norway, Sweden, and Denmark using their national transit APIs.

Sources:

Norway: Entur (Journey Planner v3)

Open GraphQL API. No key needed (but a client name header is required). Covers ALL Norwegian transit: Vy trains, buses, ferries, trams, metro. 60+ operators.

Trip Search (Oslo to Bergen example)

bash
curl -s -X POST "https://api.entur.io/journey-planner/v3/graphql" \
  -H "Content-Type: application/json" \
  -H "ET-Client-Name: $ENTUR_CLIENT_NAME" \
  -d '{"query": "{ trip(from: {place: \"NSR:StopPlace:59872\"}, to: {place: \"NSR:StopPlace:548\"}, numTripPatterns: 5) { tripPatterns { startTime duration legs { mode expectedStartTime expectedEndTime fromPlace { name } toPlace { name } line { publicCode name authority { name } } } } } }"}' | jq '.data.trip.tripPatterns[] | {start: .startTime, duration_min: (.duration / 60), legs: [.legs[] | {mode: .mode, from: .fromPlace.name, to: .toPlace.name, line: .line.publicCode, operator: .line.authority.name, depart: .expectedStartTime, arrive: .expectedEndTime}]}'

Find Stop IDs

Stop IDs use the format NSR:StopPlace:XXXXX. Find them via the Geocoder:

bash
curl -s "https://api.entur.io/geocoder/v1/autocomplete?text=Oslo%20S&size=3" \
  -H "ET-Client-Name: $ENTUR_CLIENT_NAME" | jq '[.features[] | {name: .properties.name, id: .properties.id, type: .properties.layer}]'

Key Stop IDs

City Stop ID Name
Oslo S NSR:StopPlace:59872 Oslo S
Bergen NSR:StopPlace:548 Bergen stasjon
Stavanger NSR:StopPlace:4130 Stavanger
Trondheim NSR:StopPlace:41742 Trondheim S
Bodo NSR:StopPlace:49484 Bodo stasjon

Use the Geocoder to find any stop. Works for airports, ferry terminals, bus stops too.

Departure Board

bash
curl -s -X POST "https://api.entur.io/journey-planner/v3/graphql" \
  -H "Content-Type: application/json" \
  -H "ET-Client-Name: $ENTUR_CLIENT_NAME" \
  -d '{"query": "{ stopPlace(id: \"NSR:StopPlace:59872\") { name estimatedCalls(numberOfDepartures: 10) { expectedDepartureTime destinationDisplay { frontText } serviceJourney { journeyPattern { line { publicCode name transportMode } } } } } }"}' | jq '.data.stopPlace | {name: .name, departures: [.estimatedCalls[] | {time: .expectedDepartureTime, destination: .destinationDisplay.frontText, line: .serviceJourney.journeyPattern.line.publicCode, mode: .serviceJourney.journeyPattern.line.transportMode}]}'

Notes

  • GraphQL API. POST only. One endpoint: https://api.entur.io/journey-planner/v3/graphql
  • Set ET-Client-Name header on all requests.
  • No rate limit key, but respectful usage expected.
  • Schema explorer: https://api.entur.io/graphql-explorer/journey-planner-v3
  • Covers some cross-border routes into Sweden via Vy and SJ Nord.

Sweden: ResRobot v2.1

REST API via Trafiklab. Covers ALL Swedish transit: SJ trains, regional buses, ferries, metro, commuter rail.

Authentication

RESROBOT_API_KEY in .env. Use accessId query parameter. Free key at trafiklab.se. 30,000 calls/month.

Trip Search (Stockholm to Gothenburg)

bash
curl -s "https://api.resrobot.se/v2.1/trip?originId=740000001&destId=740000002&format=json&accessId=$RESROBOT_API_KEY" | jq '[.Trip[] | {start: .LegList.Leg[0].Origin.time, date: .LegList.Leg[0].Origin.date, duration: .duration, legs: [.LegList.Leg[] | {mode: .type, name: .name, from: .Origin.name, to: .Destination.name, depart: .Origin.time, arrive: .Destination.time}]}] | .[0:5]'

Find Stop IDs

bash
curl -s "https://api.resrobot.se/v2.1/location.name?input=Stockholm&format=json&accessId=$RESROBOT_API_KEY" | jq '[.stopLocationOrCoordLocation[] | .StopLocation | {name: .name, id: .extId}] | .[0:5]'

Key Stop IDs

City Stop ID Name
Stockholm C 740000001 Stockholm Centralstation
Gothenburg C 740000002 Goteborg Centralstation
Malmo C 740000003 Malmo Centralstation
Uppsala 740000025 Uppsala Centralstation
Linkoping 740000009 Linkoping Centralstation

Trip Parameters

Param Required Description
originId Yes Departure stop ID
destId Yes Arrival stop ID
date No YYYY-MM-DD (default today)
time No HH:MM (default now)
format No json or xml
numTrips No Number of results (default 5)
products No Bitmask for transport types

Notes

  • REST API. Base: https://api.resrobot.se/v2.1/
  • Includes cross-border Oresund trains to Copenhagen.
  • No pricing data. Schedule/route only.

Denmark: Rejseplanen API 2.0

REST API (HAFAS v2.50.4.1). Covers ALL Danish transit: DSB InterCity/InterCityLyn, S-Tog, Metro, regional trains, buses, express buses, night buses, ferries, Flextur. Unique: includes fare/zone pricing data.

Docs: https://labs.rejseplanen.dk (login required). XSD schemas: https://www.rejseplanen.dk/api/xsd

Authentication

REJSEPLANEN_API_KEY in .env. Pass as accessId query parameter. Apply at labs.rejseplanen.dk. 50,000 calls/month free (non-commercial).

Common Parameters

Param Description
accessId Required. API key.
format json or xml. Always use format=json.
lang Default da. Use lang=en for English.

Product Bitmask

Value Product Description
1 IC InterCity
2 ICL InterCityLyn (express)
4 Re Regional train
8 Other ECE, Eurostar, RailJet, Togbus, DSB Udland
16 S-Tog Copenhagen commuter rail
32 Bus Local bus
64 ExpressBus Express bus (250S, X-bus)
128 NightBus Natbus
256 Flextur Demand-responsive transport
1024 Metro Copenhagen Metro

Trains only = products=31. All transit = omit param.

Key Stop IDs

Stop extId Name
København H 8600626 København H
CPH Lufthavn 8600858 CPH Lufthavn
Odense St. 8600512 Odense St.
Aarhus H 8600053 Aarhus H
Aalborg St. 8600020 Aalborg St.
Svendborg St. 8600551 Svendborg St.
Svendborg Færgehavn 100200261 Svendborg Færgehavn
Ærøskøbing Havn (færge) 100200222 Ærøskøbing Havn (færge)

Location Endpoints

location.name (Search by Name)

bash
curl -s "https://www.rejseplanen.dk/api/location.name?accessId=$REJSEPLANEN_API_KEY&input=København&format=json&type=S" | jq '[.stopLocationOrCoordLocation[] | .StopLocation | {name: .name, id: .extId, lat: .lat, lon: .lon}] | .[0:10]'
Param Required Description
input Yes Search query
type No S (stops), A (addresses), P (POI), or combine
maxNo No Max results (default 10, max 1000)
products No Product bitmask filter
r, refCoordLat, refCoordLong No Radius + reference coordinate for distance ranking

location.nearbystops (Stops Near Coordinate)

bash
curl -s "https://www.rejseplanen.dk/api/location.nearbystops?accessId=$REJSEPLANEN_API_KEY&originCoordLat=55.672793&originCoordLong=12.564590&r=500&format=json" | jq '[.stopLocationOrCoordLocation[] | .StopLocation | {name: .name, id: .extId, dist: .dist}] | .[0:10]'

Requires originCoordLat/originCoordLong. Optional: r (radius, default 1000m), maxNo, type, products.

location.details

Single location details by full HAFAS ID (URL-encoded).

location.data

Locations in a numeric ID range (llId to urId).

location.search

Filtered location search. Same params as location.name plus additional filters.

location.boundingbox

All stops in a geographic box (llLat/llLon to urLat/urLon).

addresslookup

Addresses near a coordinate. Reverse geocoding. Requires originCoordLat/originCoordLong.


Trip Planning Endpoints

trip (Route Planning)

bash
curl -s "https://www.rejseplanen.dk/api/trip?accessId=$REJSEPLANEN_API_KEY&originId=8600626&destId=8600551&date=2026-08-28&time=08:00&format=json&lang=en" | jq '[.TripList.Trip[] | {duration: .duration, legs: [.LegList.Leg[] | {name: .name, type: .type, from: .Origin.name, to: .Destination.name, depTime: .Origin.time, arrTime: .Destination.time, track: .Origin.track}]}] | .[0:3]'
Param Required Description
originId Yes* Origin stop extId
destId Yes* Destination stop extId
originCoordLat/Long Yes* Origin by coordinate (alt to originId)
destCoordLat/Long Yes* Destination by coordinate (alt to destId)
date No YYYY-MM-DD
time No HH:MM
searchForArrival No 1 = search by arrival time
numF No Forward results (1-6)
numB No Backward results (0-3)
maxChange No Max transfers (0-11)
products No Product bitmask
operators No Comma-separated operator codes
lines No Line filter
via No Via stop ID
avoid No Avoid stop ID
passlist No 1 = include intermediate stops
tariff No 1 = include fare/zone pricing
poly No 1 = include polyline
context No Scroll token for paging
originWalk/Bike/Car No Access mode distance
destWalk/Bike/Car No Egress mode distance
rtMode No FULL, INFOS, OFF, REALTIME, SERVER_DEFAULT

interval (Interval Trip Search)

All departures in a time window. Same params as trip.

recon (Trip Reconstruction)

Reconstruct trip from ctxRecon token. Params: ctx (required), poly, passlist, tariff.

reachability (All Reachable Stops)

All stops reachable within time/transfer budget. Params: originId or coordinates, duration (1-1439 min), maxChange (0-11), forward, products.


Departure and Arrival Boards

Default duration: 60 minutes.

departureBoard / arrivalBoard

bash
curl -s "https://www.rejseplanen.dk/api/departureBoard?accessId=$REJSEPLANEN_API_KEY&id=8600626&date=2026-08-28&time=08:00&format=json&lang=en" | jq '[.DepartureBoard.Departure[] | {name: .name, time: .time, direction: .direction, track: .track, rtTime: .rtTime}] | .[0:10]'

Params: id (required), date, time, duration (0-1439), maxJourneys, products, operators, passlist.

multiDepartureBoard / multiArrivalBoard

Multiple stations in one call. Pass multiple id params.

nearbyDepartureBoard / nearbyArrivalBoard

Stations near a coordinate. Params: originCoordLat/Long, r (default 1000m), maxStops (default 30).


Line and Journey Endpoints

lineinfo

Line details on a date. Requires lineId, date.

linesched

Full schedule for a line on a date. Requires lineId, date.

linesearch

All lines by operator. Requires operators (comma-separated, from datainfo).

linematch

Match lines by pattern string.

trainSearch

Find journeys by train name. Returns first/last stop only.

journeyDetail

Full stop list for a journey. Requires id (journey ref from trip/board). Optional: fromId/toId, poly, rtMode.

journeyMatch

Like trainSearch but full details for first match only.

journeypos

Real-time vehicle positions in bounding box. Requires llLat/llLon/urLat/urLon.


Tariff / Pricing Endpoints

Denmark uses zone-based pricing via Rejsekort. Unique in Scandinavia.

Endpoint Purpose
trip with tariff=1 Inline pricing in trip results
spPrice Season pass price by origin/destination
spPriceForZones Price per day for zone count in fare set
spDailyZonalPrices Full zone price matrix, all passenger types
spPriceReconstruction Price from trip reconstruction context
spRefund Season pass refund calculation
spCheck Season pass validation (SELF system)
spZoneCheck Check zone connectivity and metro zones
stRoutes Tariff routes between origin/destination
stRoutesAddon Tariff routes from single origin
stStops Tariff zones and stops (mode=DSB)
trfStop Default stop for a zone
convertZones Tariff details for context
zoneFromCoordinate Zone lookup by lat/lon

Travel Detail Endpoints

gisroute

Walking/cycling route details from trip result. Requires ctx (GIS reference).

himsearch

Real-time disruption messages. Filterable by products, operators, lines, dates, stops.


System Information

datainfo

All operators, products, categories. Use to discover operator codes.

bash
curl -s "https://www.rejseplanen.dk/api/datainfo?accessId=$REJSEPLANEN_API_KEY&format=json" | jq '[.DataInfo.operators.Operator[] | {name: .name, id: .id}] | .[0:20]'

tti

Timetable data pool info: ID, creation date, type (ST/ADR/POI), validity period.


Response Structure

  • extId: Numeric stop ID (use for queries). id is the full HAFAS string.
  • rtTime/rtDate: Real-time actual/predicted times (when different from scheduled time/date).
  • Leg type: JNY (journey), WALK (walking), TRSF (transfer).
  • JourneyDetailRef: Use with journeyDetail for full stop list.

Cross-Border Routes

Route Covered By Notes
Oslo to Stockholm Entur + ResRobot SJ trains, ~6 hours
Malmö to Copenhagen ResRobot + Rejseplanen Øresund trains, 35 min
Gothenburg to Oslo Entur + ResRobot Vy/SJ, ~4 hours
Stockholm to Copenhagen ResRobot SJ/DSB, ~5 hours via Malmö
Copenhagen to Malmö Rejseplanen Øresund trains from Danish side

When to Use

Load this skill when:

  • Planning train/bus/ferry routes between Scandinavian cities
  • Checking schedules and durations for ground transport
  • Finding stop IDs for trip planning
  • Comparing train vs flight for intra-Scandinavia legs
  • Looking up Danish transit fares/zones
  • Checking real-time disruptions (himsearch)
  • Tracking live vehicle positions (journeypos)

Do not:

  • Use for booking (search/schedule only)
  • Use for flights (use Seats.aero, SerpAPI, or Duffel)
  • Expect pricing from Entur or ResRobot (only Rejseplanen has pricing)

Search Workflows

1. "How do I get from A to B?"

Step 1: Pick API by country (Norway=Entur, Sweden=ResRobot, Denmark=Rejseplanen, cross-border=both).

Step 2: Find stop IDs:

bash
# Norway
curl -s "https://api.entur.io/geocoder/v1/autocomplete?text=Flåm&size=3" \
  -H "ET-Client-Name: $ENTUR_CLIENT_NAME" | jq '[.features[] | {name: .properties.name, id: .properties.id}]'

# Sweden
curl -s "https://api.resrobot.se/v2.1/location.name?input=Malmö&format=json&accessId=$RESROBOT_API_KEY" | jq '[.stopLocationOrCoordLocation[] | .StopLocation | {name: .name, id: .extId}] | .[0:5]'

# Denmark
curl -s "https://www.rejseplanen.dk/api/location.name?accessId=$REJSEPLANEN_API_KEY&input=Svendborg&format=json&type=S&lang=en" | jq '[.stopLocationOrCoordLocation[] | .StopLocation | {name: .name, id: .extId}] | .[0:5]'

Step 3: Trip search with date and time. Step 4 (Denmark): Add tariff=1 for pricing.

2. "What trains leave from X?"

bash
curl -s "https://www.rejseplanen.dk/api/departureBoard?accessId=$REJSEPLANEN_API_KEY&id=8600626&duration=120&products=31&format=json&lang=en" | jq '[.DepartureBoard.Departure[] | {time: .time, name: .name, direction: .direction, track: .track}]'

3. "What's near me?"

bash
curl -s "https://www.rejseplanen.dk/api/location.nearbystops?accessId=$REJSEPLANEN_API_KEY&originCoordLat=55.6736&originCoordLong=12.5681&r=500&format=json&lang=en" | jq '[.stopLocationOrCoordLocation[] | .StopLocation | {name: .name, id: .extId, dist: .dist}]'

Or nearbyDepartureBoard to skip stop lookup.

4. "What ferry to Ærø?"

bash
curl -s "https://www.rejseplanen.dk/api/trip?accessId=$REJSEPLANEN_API_KEY&originId=100200261&destId=100200222&date=2026-08-28&time=08:00&format=json&lang=en" | jq '[.TripList.Trip[] | {legs: [.LegList.Leg[] | {name: .name, from: .Origin.name, to: .Destination.name, depTime: .Origin.time, arrTime: .Destination.time}]}] | .[0:5]'

5. "I know the train number."

trainSearch to find it, then journeyDetail with the ref for full stops.

6. "What can I reach in 2 hours?"

bash
curl -s "https://www.rejseplanen.dk/api/reachability?accessId=$REJSEPLANEN_API_KEY&originId=8600626&duration=120&maxChange=2&format=json" | jq '.'

7. "Any disruptions?"

bash
curl -s "https://www.rejseplanen.dk/api/himsearch?accessId=$REJSEPLANEN_API_KEY&format=json&lang=en" | jq '.'

8. Multi-country trips

Search from both ends. Øresund trains appear in both ResRobot and Rejseplanen.

Tips

  • Always format=json. Always lang=en for Rejseplanen.
  • Use extId (numeric) for queries, not the long HAFAS id string.
  • Check rtTime/rtDate for delays.
  • passlist=1 shows intermediate stops.
  • maxChange=0 for direct connections only.
  • Page with numF/numB or context scroll tokens.
  • Product bitmask is additive. Omit for all types.

Expand your agent's capabilities with these related and highly-rated skills.

borski/travel-hacking-toolkit

chase-travel

387 22
Explore
borski/travel-hacking-toolkit

atlas-obscura

Search Atlas Obscura for weird, wonderful, and hidden gem places near any destination. Find the interesting stuff, not boring plaques. Search by coordinates, get full details with descriptions and images.

387 22
Explore
borski/travel-hacking-toolkit

rapidapi

Search Google Flights Live and Booking.com Live via RapidAPI. Use as a secondary source for flight cash prices and hotel availability/pricing. Triggers on "Booking.com", "booking prices", "RapidAPI", "secondary flight search", "hotel prices on Booking", or when SerpAPI results need a second opinion. Also useful for vacation rental pricing.

387 22
Explore
borski/travel-hacking-toolkit

premium-hotels

Search Amex Fine Hotels & Resorts (FHR), The Hotel Collection (THC), and Chase Sapphire Reserve Hotel Collection (Chase Edit) properties by city. Compare credits, benefits, and stacking opportunities across programs.

387 22
Explore
borski/travel-hacking-toolkit

google-flights

Search Google Flights for flight prices and schedules using browser automation. Use when user asks to search flights, find airfare, compare prices, check flight availability, or look up routes. Covers ALL airlines including Southwest (which no API can reach). Triggers include "search flights", "find flights", "how much is a flight", "flights from X to Y", "cheapest flight", "flight prices", "airfare", "flight schedule", "nonstop flights", "when should I fly".

387 22
Explore
borski/travel-hacking-toolkit

ignav

Search for flights using the Ignav API. Fast REST API for flight prices and booking links. Use alongside other flight sources for price comparison. Always include in every flight search. Triggers on any flight-related search request.

387 22
Explore

Didn't find tool you were looking for?

Be as detailed as possible for better results