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.
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:
- Entur (Norway) — Open GraphQL API for all Norwegian transit
- ResRobot (Sweden) — REST API via Trafiklab for all Swedish transit
- Rejseplanen (Denmark) — REST API (HAFAS) for all Danish transit including pricing
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
| 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
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-Nameheader 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
| 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)
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)
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]'
| 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
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.
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).idis the full HAFAS string.rtTime/rtDate: Real-time actual/predicted times (when different from scheduledtime/date).- Leg
type:JNY(journey),WALK(walking),TRSF(transfer). JourneyDetailRef: Use withjourneyDetailfor 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:
# 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. Alwayslang=enfor Rejseplanen. - Use
extId(numeric) for queries, not the long HAFASidstring. - Check
rtTime/rtDatefor delays. passlist=1shows intermediate stops.maxChange=0for direct connections only.- Page with
numF/numBorcontextscroll tokens. - Product bitmask is additive. Omit for all types.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
chase-travel
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.
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.
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.
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".
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.
Didn't find tool you were looking for?