mcpField
S
scandinavia-transit

scandinavia-transit

github.com/borskiOther78069 installsUpdated 2d ago
Repository

About scandinavia-transit

scandinavia-transit is a skill for searching ground transport in Norway, Sweden, and Denmark, intended for trip planning. It uses the three countries’ transit APIs to find routes and schedules, and includes fare information for Denmark.

Key features

  1. 01Norwegian route and schedule search
  2. 02Swedish route and schedule search
  3. 03Danish route and schedule search
  4. 04Stop and location lookup
  5. 05Danish fare and zone information

Use cases

  1. 01Plan a trip from Oslo to Bergen
  2. 02Find train routes between Swedish cities
  3. 03Check departures from a Danish station
  4. 04Review Danish routes and fare zones

Install

Copy the command and run it in a terminal.

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

After installing, type/scandinavia-transitin Claude Code to confirm it loaded.

Details

From the project README

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)
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:

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
CityStop IDName
Oslo SNSR:StopPlace:59872Oslo S
BergenNSR:StopPlace:548Bergen stasjon
StavangerNSR:StopPlace:4130Stavanger
TrondheimNSR:StopPlace:41742Trondheim S
BodoNSR:StopPlace:49484Bodo stasjon

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

Departure Board
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)
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
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
CityStop IDName
Stockholm C740000001Stockholm Centralstation
Gothenburg C740000002Goteborg Centralstation
Malmo C740000003Malmo Centralstation
Uppsala740000025Uppsala Centralstation
Linkoping740000009Linkoping Centralstation
Trip Parameters
ParamRequiredDescription
originIdYesDeparture stop ID
destIdYesArrival stop ID
dateNoYYYY-MM-DD (default today)
timeNoHH:MM (default now)
formatNojson or xml
numTripsNoNumber of results (default 5)
productsNoBitmask 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
ParamDescription
accessIdRequired. API key.
formatjson or xml. Always use format=json.
langDefault da. Use lang=en for English.
Product Bitmask
ValueProductDescription
1ICInterCity
2ICLInterCityLyn (express)
4ReRegional train
8OtherECE, Eurostar, RailJet, Togbus, DSB Udland
16S-TogCopenhagen commuter rail
32BusLocal bus
64ExpressBusExpress bus (250S, X-bus)
128NightBusNatbus
256FlexturDemand-responsive transport
1024MetroCopenhagen Metro

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

Key Stop IDs
StopextIdName
København H8600626København H
CPH Lufthavn8600858CPH Lufthavn
Odense St.8600512Odense St.
Aarhus H8600053Aarhus H
Aalborg St.8600020Aalborg St.
Svendborg St.8600551Svendborg St.
Svendborg Færgehavn100200261Svendborg Færgehavn
Ærøskøbing Havn (færge)100200222Ærøskøbing Havn (færge)

Location Endpoints
location.name (Search by Name)
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]'
ParamRequiredDescription
inputYesSearch query
typeNoS (stops), A (addresses), P (POI), or combine
maxNoNoMax results (default 10, max 1000)
productsNoProduct bitmask filter
r, refCoordLat, refCoordLongNoRadius + reference coordinate for distance ranking
location.nearbystops (Stops Near Coordinate)
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)
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]'
ParamRequiredDescription
originIdYes*Origin stop extId
destIdYes*Destination stop extId
originCoordLat/LongYes*Origin by coordinate (alt to originId)
destCoordLat/LongYes*Destination by coordinate (alt to destId)
dateNoYYYY-MM-DD
timeNoHH:MM
searchForArrivalNo1 = search by arrival time
numFNoForward results (1-6)
numBNoBackward results (0-3)
maxChangeNoMax transfers (0-11)
productsNoProduct bitmask
operatorsNoComma-separated operator codes
linesNoLine filter
viaNoVia stop ID
avoidNoAvoid stop ID
passlistNo1 = include intermediate stops
tariffNo1 = include fare/zone pricing
polyNo1 = include polyline
contextNoScroll token for paging
originWalk/Bike/CarNoAccess mode distance
destWalk/Bike/CarNoEgress mode distance
rtModeNoFULL, 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
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.

EndpointPurpose
trip with tariff=1Inline pricing in trip results
spPriceSeason pass price by origin/destination
spPriceForZonesPrice per day for zone count in fare set
spDailyZonalPricesFull zone price matrix, all passenger types
spPriceReconstructionPrice from trip reconstruction context
spRefundSeason pass refund calculation
spCheckSeason pass validation (SELF system)
spZoneCheckCheck zone connectivity and metro zones
stRoutesTariff routes between origin/destination
stRoutesAddonTariff routes from single origin
stStopsTariff zones and stops (mode=DSB)
trfStopDefault stop for a zone
convertZonesTariff details for context
zoneFromCoordinateZone 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.

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

RouteCovered ByNotes
Oslo to StockholmEntur + ResRobotSJ trains, ~6 hours
Malmö to CopenhagenResRobot + RejseplanenØresund trains, 35 min
Gothenburg to OsloEntur + ResRobotVy/SJ, ~4 hours
Stockholm to CopenhagenResRobotSJ/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:

# 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?"
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?"
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ø?"
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?"
curl -s "https://www.rejseplanen.dk/api/reachability?accessId=$REJSEPLANEN_API_KEY&originId=8600626&duration=120&maxChange=2&format=json" | jq '.'
7. "Any disruptions?"
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.

Comments

Markdown supported