Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Search trains, buses, and ferries in Norway (Entur), Sweden (ResRobot), and Denmark (Rejseplanen). Intra-Scandinavia ground transport with schedules and Danish fare pricing.
.claude/skills/borski-scandinavia-transit/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 185% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 317% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 370% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 263% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 351% | 0% |
Search ground transport (trains, buses, ferries) within Norway, Sweden, and Denmark using their national transit APIs.
Sources:
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.
bashcurl -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}]}'
Stop IDs use the format NSR:StopPlace:XXXXX. Find them via the Geocoder:
bashcurl -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}]'
| 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.
bashcurl -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}]}'
https://api.entur.io/journey-planner/v3/graphqlET-Client-Name header on all requests.REST API via Trafiklab. Covers ALL Swedish transit: SJ trains, regional buses, ferries, metro, commuter rail.
RESROBOT_API_KEY in .env. Use accessId query parameter. Free key at trafiklab.se. 30,000 calls/month.
bashcurl -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]'
bashcurl -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]'
| 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 |
| 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 |
https://api.resrobot.se/v2.1/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
REJSEPLANEN_API_KEY in .env. Pass as accessId query parameter. Apply at labs.rejseplanen.dk. 50,000 calls/month free (non-commercial).
| Param | Description | |-------|-------------| | accessId | Required. API key. | | format | json or xml. Always use format=json. | | lang | Default da. Use lang=en for English. |
| 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.
| 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) |
bashcurl -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 |
bashcurl -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.
Single location details by full HAFAS ID (URL-encoded).
Locations in a numeric ID range (llId to urId).
Filtered location search. Same params as location.name plus additional filters.
All stops in a geographic box (llLat/llLon to urLat/urLon).
Addresses near a coordinate. Reverse geocoding. Requires originCoordLat/originCoordLong.
bashcurl -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 |
All departures in a time window. Same params as trip.
Reconstruct trip from ctxRecon token. Params: ctx (required), poly, passlist, tariff.
All stops reachable within time/transfer budget. Params: originId or coordinates, duration (1-1439 min), maxChange (0-11), forward, products.
Default duration: 60 minutes.
bashcurl -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.
Multiple stations in one call. Pass multiple id params.
Stations near a coordinate. Params: originCoordLat/Long, r (default 1000m), maxStops (default 30).
Line details on a date. Requires lineId, date.
Full schedule for a line on a date. Requires lineId, date.
All lines by operator. Requires operators (comma-separated, from datainfo).
Match lines by pattern string.
Find journeys by train name. Returns first/last stop only.
Full stop list for a journey. Requires id (journey ref from trip/board). Optional: fromId/toId, poly, rtMode.
Like trainSearch but full details for first match only.
Real-time vehicle positions in bounding box. Requires llLat/llLon/urLat/urLon.
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 |
Walking/cycling route details from trip result. Requires ctx (GIS reference).
Real-time disruption messages. Filterable by products, operators, lines, dates, stops.
All operators, products, categories. Use to discover operator codes.
bashcurl -s "https://www.rejseplanen.dk/api/datainfo?accessId=$REJSEPLANEN_API_KEY&format=json" | jq '[.DataInfo.operators.Operator[] | {name: .name, id: .id}] | .[0:20]'
Timetable data pool info: ID, creation date, type (ST/ADR/POI), validity period.
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).type: JNY (journey), WALK (walking), TRSF (transfer).JourneyDetailRef: Use with journeyDetail for full stop list.| 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 |
Load this skill when:
himsearch)journeypos)Do not:
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.
bashcurl -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}]'
bashcurl -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.
bashcurl -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]'
trainSearch to find it, then journeyDetail with the ref for full stops.
bashcurl -s "https://www.rejseplanen.dk/api/reachability?accessId=$REJSEPLANEN_API_KEY&originId=8600626&duration=120&maxChange=2&format=json" | jq '.'
bashcurl -s "https://www.rejseplanen.dk/api/himsearch?accessId=$REJSEPLANEN_API_KEY&format=json&lang=en" | jq '.'
Search from both ends. Øresund trains appear in both ResRobot and Rejseplanen.
format=json. Always lang=en for Rejseplanen.extId (numeric) for queries, not the long HAFAS id string.rtTime/rtDate for delays.passlist=1 shows intermediate stops.maxChange=0 for direct connections only.numF/numB or context scroll tokens.Other measured skills in the registry, with their headline benchmark lift.