Scandinavia Transit logo

Scandinavia Transit

Community
borski
scandinavia-transit

Search trains, buses, and ferries in Norway (Entur), Sweden (ResRobot), and Denmark (Rejseplanen). Intra-Scandinavia ground transport with schedules and Danish fare pricing.

Overview

Publisherborski
Repositorytravel-hacking-toolkit
Skill namescandinavia-transit
Stars
690
Forks
75
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by borski on GitHub. Read the source before you install it.

Installation

Install the Scandinavia Transit AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/borski/travel-hacking-toolkit/tree/main/plugins/travel-hacking-toolkit/skills/scandinavia-transit
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/borski/travel-hacking-toolkit.git /tmp/travel-hacking-toolkit
mkdir -p .claude/skills
cp -r /tmp/travel-hacking-toolkit/plugins/travel-hacking-toolkit/skills/scandinavia-transit .claude/skills/scandinavia-transit
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Scandinavia Transit in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Scandinavia Transit on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Scandinavia Transit is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

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

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

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

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)
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]'
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)
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]'
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
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.

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.

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

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:

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.

Frequently asked questions

What does the Scandinavia Transit AI skill do?

Search trains, buses, and ferries in Norway (Entur), Sweden (ResRobot), and Denmark (Rejseplanen). Intra-Scandinavia ground transport with schedules and Danish fare pricing.

Why use Scandinavia Transit on TypingMind?

Because you install it once and use it with any model. Scandinavia Transit is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Scandinavia Transit in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/borski/travel-hacking-toolkit/tree/main/plugins/travel-hacking-toolkit/skills/scandinavia-transit. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Scandinavia Transit?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Scandinavia Transit?

As many as you like. As long as a model supports skills, you can use Scandinavia Transit with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Scandinavia Transit AI skill free?

Yes. It is published on GitHub by borski under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇