Skill: manmal/oebb-scotty (version 1.0.0)

--- name: oebb-scotty description: Austrian rail travel planner (ÖBB Scotty). Use when planning train journeys in Austria, checking departures/arrivals at stations, or looking for service disruptions. Covers ÖBB trains, S-Bahn, regional trains, and connections to neighboring countries. --- # ÖBB Scotty API Query Austria's public transport for trip planning, station departures, and service alerts via the HAFAS mgate API. ## Quick Reference | Method | Purpose | |--------|---------| | `LocMatch` | Search for stations/stops by name | | `TripSearch` | Plan a journey between two locations | | `StationBoard` | Get departures/arrivals at a station | | `HimSearch` | Get service alerts and disruptions | **Base URL:** `https://fahrplan.oebb.at/bin/mgate.exe` --- ## Authentication All requests require these headers in the JSON body: ```json { "id": "1", "ver": "1.67", "lang": "deu", "auth": {"type": "AID", "aid": "OWDL4fE4ixNiPBBm"}, "client": {"id": "OEBB", "type": "WEB", "name": "webapp", "l": "vs_webapp"}, "formatted": false, "svcReqL": [...] } ``` --- ## 1. Location Search (`LocMatch`) Search for stations, stops, addresses, or POIs by name. ### Request ```bash curl -s -X POST "https://fahrplan.oebb.at/bin/mgate.exe" \ -H "Content-Type: application/json" \ -d '{ "id":"1","ver":"1.67","lang":"deu", "auth":{"type":"AID","aid":"OWDL4fE4ixNiPBBm"}, "client":{"id":"OEBB","type":"WEB","name":"webapp","l":"vs_webapp"}, "formatted":false, "svcReqL":[{ "req":{"input":{"field":"S","loc":{"name":"Wien Hbf","type":"ALL"},"maxLoc":10}}, "meth":"LocMatch" }] }' ``` ### Response Structure ```json { "svcResL": [{ "res": { "match": { "locL": [{ "lid": "A=1@O=Wien Hbf (U)@X=16377950@Y=48184986@U=181@L=1290401@", "type": "S", "name": "Wien Hbf (U)", "extId": "1290401", "crd": { "x": 16377950, "y": 48184986 }, "pCls": 6015 }] } } }] } ``` ### Location Types | Type | Description | |------|-------------| | `S` | Station/Stop | | `A` | Address | | `P` | POI (Point of Interest) | ### Key Fields | Field | Description | |-------|-------------| | `lid` | Location ID string (use in TripSearch) | | `extId` | External station ID | | `name` | Station name | | `crd.x/y` | Coordinates (x=lon, y=lat, scaled by 10^6) | | `pCls` | Product class bitmask | --- ## 2. Trip Search (`TripSearch`) Plan a journey between two locations. ### Request ```bash curl -s -X POST "https://fahrplan.oebb.at/bin/mgate.exe" \ -H "Content-Type: application/json" \ -d '{ "id":"1","ver":"1.67","lang":"deu", "auth":{"type":"AID","aid":"OWDL4fE4ixNiPBBm"}, "client":{"id":"OEBB","type":"WEB","name":"webapp","l":"vs_webapp"}, "formatted":false, "svcReqL":[{ "req":{ "depLocL":[{"lid":"A=1@O=Wien Hbf@L=8103000@","type":"S"}], "arrLocL":[{"lid":"A=1@O=Salzburg Hbf@L=8100002@","type":"S"}], "jnyFltrL":[{"type":"PROD","mode":"INC","value":"1023"}], "getPolyline":false, "getPasslist":true, "outDate":"20260109", "outTime":"080000", "outFrwd":true, "numF":5 }, "meth":"TripSearch" }] }' ``` ### Parameters | Param | Description | |-------|-------------| | `depLocL` | Departure location(s) - use `lid` from LocMatch | | `arrLocL` | Arrival location(s) | | `outDate` | Departure date (YYYYMMDD) | | `outTime` | Departure time (HHMMSS) | | `outFrwd` | `true` = search forward, `false` = search backward | | `numF` | Number of connections to return | | `jnyFltrL` | Product filter (see below) | | `getPasslist` | Include intermediate stops | ### Product Filter Values | Bit | Value | Product | |-----|-------|---------| | 0 | 1 | ICE/RJX (High-speed) | | 1 | 2 | IC/EC (InterCity) | | 2 | 4 | NJ (Night trains) | | 3 | 8 | D/EN (Express) | | 4 | 16 | REX/R (Regional Express) | | 5 | 32 | S-Bahn | | 6 | 64 | Bus | | 7 | 128 | Ferry | | 8 | 256 | U-Bahn | | 9 | 512 | Tram | Use `1023` for all products, or sum specific bits. ### Response Structure ```json { "svcResL": [{ "res": { "outConL": [{ "date": "20260109", "dur": "025200", "chg": 0, "dep": { "dTimeS": "075700", "dPltfS": {"txt": "8A-B"} }, "arr": { "aTimeS": "104900", "aPltfS": {"txt": "7"} }, "secL": [{ "type": "JNY", "jny": { "prodX": 0, "dirTxt": "Salzburg Hbf", "stopL": [...] } }] }], "common": { "locL": [...], "prodL": [...] } } }] } ``` ### Key Connection Fields | Field | Description | |-------|-------------| | `dur` | Duration (HHMMSS) | | `chg` | Number of changes | | `dTimeS` | Scheduled departure | | `dTimeR` | Real-time departure (if available) | | `aTimeS` | Scheduled arrival | | `aTimeR` | Real-time arrival (if available) | | `dPltfS.txt` | Departure platform | | `aPltfS.txt` | Arrival platform | | `secL` | Journey sections (legs) | | `secL[].jny.prodX` | Index into `common.prodL[]` for train name | ### Understanding prodX (Product Index) **Important:** The `prodX` field in journey sections is an index into the `common.prodL[]` array, NOT the train name itself. To get the actual train name (e.g., "S7", "RJX 662"), you must look up `common.prodL[prodX].name`. ### Extracting Trip Summaries with jq The raw TripSearch response is very verbose. Use this jq filter to extract a concise summary with resolved train names: ```bash curl -s -X POST "https://fahrplan.oebb.at/bin/mgate.exe" \ -H "Content-Type: application/json" \ -d '{ ... }' | jq ' .svcResL[0].res as $r | $r.common.prodL as $prods | [$r.outConL[] | { dep: .dep.dTimeS, arr: .arr.aTimeS, depPlatform: .dep.dPltfS.txt, arrPlatform: .arr.aPltfS.txt, dur: .dur, chg: .chg, legs: [.secL[] | select(.type == "JNY") | { train: $prods[.jny.prodX].name, dir: .jny.dirTxt, dep: .dep.dTimeS, arr: .arr.aTimeS, depPlatform: .dep.dPltfS.txt, arrPlatform: .arr.aPltfS.txt }] }]' ``` Example output: ```json [ { "dep": "213900", "arr": "221100", "depPlatform": "1", "arrPlatform": "3A-B", "dur": "003200", "chg": 0, "legs": [{"train": "S 7", "dir": "Flughafen Wien Bahnhof", "dep": "213900", "arr": "221100", ...}] } ] ``` --- ## 3. Station Board (`StationBoard`) Get departures or arrivals at a station. ### Request ```bash curl -s -X POST "https://fahrplan.oebb.at/bin/mgate.exe" \ -H "Content-Type: application/json" \ -d '{ "id":"1","ver":"1.67","lang":"deu", "auth":{"type":"AID","aid":"OWDL4fE4ixNiPBBm"}, "client":{"id":"OEBB","type":"WEB","name":"webapp","l":"vs_webapp"}, "formatted":false, "svcReqL":[{ "req":{ "stbLoc":{"lid":"A=1@O=Wien Hbf@L=8103000@","type":"S"}, "date":"20260109", "time":"080000", "type":"DEP", "maxJny":20 }, "meth":"StationBoard" }] }' ``` ### Parameters | Param | Description | |-------|-------------| | `stbLoc` | Station location | | `date` | Date (YYYYMMDD) | | `time` | Time (HHMMSS) | | `type` | `DEP` (departures) or `ARR` (arrivals) | | `maxJny` | Maximum number of journeys | ### Response Structure ```json { "svcResL": [{ "res": { "jnyL": [{ "prodX": 0, "dirTxt": "Salzburg Hbf", "stbStop": { "dTimeS": "080000", "dPltfS": {"txt": "8A-B"} } }], "common": { "prodL": [{ "name": "RJX 662", "cls": 1, "prodCtx": {"catOutL": "Railjet Xpress"} }] } } }] } ``` --- ## 4. Service Alerts (`HimSearch`) Get current disruptions and service information. ### Request ```bash curl -s -X POST "https://fahrplan.oebb.at/bin/mgate.exe" \ -H "Content-Type: application/json" \ -d '{ "id":"1","ver":"1.67","lang":"deu", "auth":{"type":"AID","aid":"OWDL4fE4ixNiPBBm"}, "client":{"id":"OEBB","type":"WEB","name":"webapp","l":"vs_webapp"}, "formatted":false, "svcReqL":[{ "req":{ "himFltrL":[{"type":"PROD","mode":"INC","value":"255"}], "maxNum":20 }, "meth":"HimSearch" }] }' ``` ### Response Structure ```json { "svcResL": [{ "res": { "msgL": [{ "hid": "HIM_FREETEXT_843858", "head": "Verringertes Sitzplatzangebot", "text": "Wegen einer technischen Störung...", "prio": 0, "sDate": "20260108", "eDate": "20260108" }] } }] } ``` --- ## Common Station IDs | Station | extId | |---------|-------| | Wien Hbf | 8103000 | | Wien Meidling | 8100514 | | Wien Westbahnhof | 8101003 | | Salzburg Hbf | 8100002 | | Linz Hbf | 8100013 | | Graz Hbf | 8100173 | | Innsbruck Hbf | 8100108 | | Klagenfurt Hbf | 8100085 | | St. Pölten Hbf | 8100008 | | Wr. Neustadt Hbf | 8100516 | --- ## Time Format - Dates: `YYYYMMDD` (e.g., `20260109`) - Times: `HHMMSS` (e.g., `080000` = 08:00:00) - Duration: `HHMMSS` (e.g., `025200` = 2h 52m) --- ## Error Handling Check `err` field in response: ```json { "err": "OK", // Success "err": "PARSE", // Invalid request format "err": "NO_MATCH", // No results found "errTxt": "..." // Error details } ``` --- ## Product Classes (cls values) | cls | Product | |-----|---------| | 1 | ICE/RJX | | 2 | IC/EC | | 4 | Night trains | | 8 | NJ/EN | | 16 | REX/Regional | | 32 | S-Bahn | | 64 | Bus | | 128 | Ferry | | 256 | U-Bahn | | 512 | Tram | | 1024 | On-demand | | 2048 | Other |

path: arrivals.sh
content: #!/bin/bash
# Get arrivals at an ÖBB station
# Usage: ./arrivals.sh <station-name> [date] [time] [max-results]
#
# Date format: YYYYMMDD (default: today)
# Time format: HHMM (default: now)
# Example: ./arrivals.sh "Salzburg Hbf" 20260109 0800 20

STATION="${1:-}"
DATE="${2:-$(date +%Y%m%d)}"
TIME="${3:-$(date +%H%M)}00"
MAX="${4:-20}"

if [ -z "$STATION" ]; then
    echo "Usage: $0 <station-name> [date] [time] [max-results]"
    echo "Example: $0 'Salzburg Hbf' 20260109 0800 20"
    exit 1
fi

# Ensure time has seconds
if [ ${#TIME} -eq 4 ]; then
    TIME="${TIME}00"
fi

# First, resolve station name to LID
STATION_LID=$(curl -s -X POST "https://fahrplan.oebb.at/bin/mgate.exe" \
  -H "Content-Type: application/json" \
  -d '{
    "id":"1","ver":"1.67","lang":"deu",
    "auth":{"type":"AID","aid":"OWDL4fE4ixNiPBBm"},
    "client":{"id":"OEBB","type":"WEB","name":"webapp","l":"vs_webapp"},
    "formatted":false,
    "svcReqL":[{
      "req":{"input":{"field":"S","loc":{"name":"'"$STATION"'","type":"S"},"maxLoc":1}},
      "meth":"LocMatch"
    }]
  }' | jq -r '.svcResL[0].res.match.locL[0].lid // empty')

if [ -z "$STATION_LID" ]; then
    echo "Error: Could not find station: $STATION"
    exit 1
fi

# Get arrivals
curl -s -X POST "https://fahrplan.oebb.at/bin/mgate.exe" \
  -H "Content-Type: application/json" \
  -d '{
    "id":"1","ver":"1.67","lang":"deu",
    "auth":{"type":"AID","aid":"OWDL4fE4ixNiPBBm"},
    "client":{"id":"OEBB","type":"WEB","name":"webapp","l":"vs_webapp"},
    "formatted":false,
    "svcReqL":[{
      "req":{
        "stbLoc":{"lid":"'"$STATION_LID"'","type":"S"},
        "date":"'"$DATE"'",
        "time":"'"$TIME"'",
        "type":"ARR",
        "maxJny":'"$MAX"'
      },
      "meth":"StationBoard"
    }]
  }' | jq '
    .svcResL[0].res as $res |
    {
      station: ($res.common.locL[0].name),
      date: "'"$DATE"'",
      arrivals: [
        $res.jnyL[] | {
          time: (.stbStop.aTimeS | "\(.[0:2]):\(.[2:4])"),
          timeReal: (if .stbStop.aTimeR then (.stbStop.aTimeR | "\(.[0:2]):\(.[2:4])") else null end),
          delay: (if .stbStop.aTimeR and .stbStop.aTimeS then
            (((.stbStop.aTimeR[0:2] | tonumber) * 60 + (.stbStop.aTimeR[2:4] | tonumber)) -
             ((.stbStop.aTimeS[0:2] | tonumber) * 60 + (.stbStop.aTimeS[2:4] | tonumber)))
          else null end),
          platform: .stbStop.aPltfS.txt,
          train: ($res.common.prodL[.prodX].name // ""),
          category: ($res.common.prodL[.prodX].prodCtx.catOutL // ""),
          from: .dirTxt,
          cancelled: (.stbStop.aCncl // false)
        }
      ]
    }
  '


path: departures.sh
content: #!/bin/bash
# Get departures from an ÖBB station
# Usage: ./departures.sh <station-name> [date] [time] [max-results]
#
# Date format: YYYYMMDD (default: today)
# Time format: HHMM (default: now)
# Example: ./departures.sh "Wien Hbf" 20260109 0800 20

STATION="${1:-}"
DATE="${2:-$(date +%Y%m%d)}"
TIME="${3:-$(date +%H%M)}00"
MAX="${4:-20}"

if [ -z "$STATION" ]; then
    echo "Usage: $0 <station-name> [date] [time] [max-results]"
    echo "Example: $0 'Wien Hbf' 20260109 0800 20"
    exit 1
fi

# Ensure time has seconds
if [ ${#TIME} -eq 4 ]; then
    TIME="${TIME}00"
fi

# First, resolve station name to LID
STATION_LID=$(curl -s -X POST "https://fahrplan.oebb.at/bin/mgate.exe" \
  -H "Content-Type: application/json" \
  -d '{
    "id":"1","ver":"1.67","lang":"deu",
    "auth":{"type":"AID","aid":"OWDL4fE4ixNiPBBm"},
    "client":{"id":"OEBB","type":"WEB","name":"webapp","l":"vs_webapp"},
    "formatted":false,
    "svcReqL":[{
      "req":{"input":{"field":"S","loc":{"name":"'"$STATION"'","type":"S"},"maxLoc":1}},
      "meth":"LocMatch"
    }]
  }' | jq -r '.svcResL[0].res.match.locL[0].lid // empty')

if [ -z "$STATION_LID" ]; then
    echo "Error: Could not find station: $STATION"
    exit 1
fi

# Get departures
curl -s -X POST "https://fahrplan.oebb.at/bin/mgate.exe" \
  -H "Content-Type: application/json" \
  -d '{
    "id":"1","ver":"1.67","lang":"deu",
    "auth":{"type":"AID","aid":"OWDL4fE4ixNiPBBm"},
    "client":{"id":"OEBB","type":"WEB","name":"webapp","l":"vs_webapp"},
    "formatted":false,
    "svcReqL":[{
      "req":{
        "stbLoc":{"lid":"'"$STATION_LID"'","type":"S"},
        "date":"'"$DATE"'",
        "time":"'"$TIME"'",
        "type":"DEP",
        "maxJny":'"$MAX"'
      },
      "meth":"StationBoard"
    }]
  }' | jq '
    .svcResL[0].res as $res |
    {
      station: ($res.common.locL[0].name),
      date: "'"$DATE"'",
      departures: [
        $res.jnyL[] | {
          time: (.stbStop.dTimeS | "\(.[0:2]):\(.[2:4])"),
          timeReal: (if .stbStop.dTimeR then (.stbStop.dTimeR | "\(.[0:2]):\(.[2:4])") else null end),
          delay: (if .stbStop.dTimeR and .stbStop.dTimeS then
            (((.stbStop.dTimeR[0:2] | tonumber) * 60 + (.stbStop.dTimeR[2:4] | tonumber)) -
             ((.stbStop.dTimeS[0:2] | tonumber) * 60 + (.stbStop.dTimeS[2:4] | tonumber)))
          else null end),
          platform: .stbStop.dPltfS.txt,
          train: ($res.common.prodL[.prodX].name // ""),
          category: ($res.common.prodL[.prodX].prodCtx.catOutL // ""),
          direction: .dirTxt,
          cancelled: (.stbStop.dCncl // false)
        }
      ]
    }
  '


path: disruptions.sh
content: #!/bin/bash
# Get current service disruptions and alerts
# Usage: ./disruptions.sh [max-results]
#
# Example: ./disruptions.sh 20

MAX="${1:-20}"

curl -s -X POST "https://fahrplan.oebb.at/bin/mgate.exe" \
  -H "Content-Type: application/json" \
  -d '{
    "id":"1","ver":"1.67","lang":"deu",
    "auth":{"type":"AID","aid":"OWDL4fE4ixNiPBBm"},
    "client":{"id":"OEBB","type":"WEB","name":"webapp","l":"vs_webapp"},
    "formatted":false,
    "svcReqL":[{
      "req":{
        "himFltrL":[{"type":"PROD","mode":"INC","value":"255"}],
        "maxNum":'"$MAX"'
      },
      "meth":"HimSearch"
    }]
  }' | jq '
    .svcResL[0].res.msgL[] | {
      id: .hid,
      title: .head,
      text: (.text | gsub("<[^>]*>"; "")),
      priority: .prio,
      startDate: .sDate,
      endDate: .eDate,
      active: .act
    }
  '


path: README.md
content: # ÖBB Scotty Skill

Austrian rail travel planner using the ÖBB Scotty HAFAS API.

## Tools

### search-station.sh
Search for stations by name.

```bash
./search-station.sh "Wien Hbf"
./search-station.sh "Salzburg"
./search-station.sh "Flughafen Wien"
```

### trip.sh
Plan a journey between two stations.

```bash
# Basic usage
./trip.sh "Wien Hbf" "Salzburg Hbf"

# With date and time
./trip.sh "Wien Hbf" "Salzburg Hbf" 20260109 0800

# With custom number of results
./trip.sh "Graz Hbf" "Innsbruck Hbf" 20260109 1200 10
```

### departures.sh
Get departures from a station.

```bash
# Next departures
./departures.sh "Wien Hbf"

# Specific date/time
./departures.sh "Wien Hbf" 20260109 0800 20
```

### arrivals.sh
Get arrivals at a station.

```bash
# Next arrivals
./arrivals.sh "Salzburg Hbf"

# Specific date/time
./arrivals.sh "Linz Hbf" 20260109 1200 15
```

### disruptions.sh
Get current service disruptions.

```bash
./disruptions.sh
./disruptions.sh 50  # Get more results
```

## Example Output

### Trip Search
```json
{
  "date": "20260109",
  "departure": {
    "time": "07:57",
    "platform": "8A-B",
    "station": "Wien Hbf"
  },
  "arrival": {
    "time": "10:49",
    "platform": "7",
    "station": "Salzburg Hbf"
  },
  "duration": "02h 52m",
  "changes": 0,
  "legs": [{
    "train": "IC 544",
    "category": "InterCity",
    "direction": "Salzburg Hbf"
  }]
}
```

### Departures
```json
{
  "station": "Wien Hbf",
  "departures": [{
    "time": "08:00",
    "platform": "N2",
    "train": "Bus 200",
    "direction": "Eisenstadt Busbahnhof"
  }]
}
```

## Common Stations

| Station | Notes |
|---------|-------|
| Wien Hbf | Vienna main station |
| Wien Meidling | Vienna south |
| Wien Westbahnhof | Vienna west |
| Salzburg Hbf | Salzburg main |
| Linz Hbf | Linz main |
| Graz Hbf | Graz main |
| Innsbruck Hbf | Innsbruck main |
| Klagenfurt Hbf | Klagenfurt main |
| Flughafen Wien | Vienna Airport |


path: search-station.sh
content: #!/bin/bash
# Search ÖBB stations/stops by name
# Usage: ./search-station.sh <name>
#
# Returns: Station name, ID, and coordinates

QUERY="${1:-}"

if [ -z "$QUERY" ]; then
    echo "Usage: $0 <station-name>"
    echo "Example: $0 'Wien Hbf'"
    exit 1
fi

curl -s -X POST "https://fahrplan.oebb.at/bin/mgate.exe" \
  -H "Content-Type: application/json" \
  -d '{
    "id":"1","ver":"1.67","lang":"deu",
    "auth":{"type":"AID","aid":"OWDL4fE4ixNiPBBm"},
    "client":{"id":"OEBB","type":"WEB","name":"webapp","l":"vs_webapp"},
    "formatted":false,
    "svcReqL":[{
      "req":{"input":{"field":"S","loc":{"name":"'"$QUERY"'","type":"ALL"},"maxLoc":10}},
      "meth":"LocMatch"
    }]
  }' | jq -r '
    .svcResL[0].res.match.locL[] |
    select(.type == "S") |
    {
      name: .name,
      id: .extId,
      lid: .lid,
      lat: ((.crd.y // 0) / 1000000),
      lon: ((.crd.x // 0) / 1000000)
    }
  '


path: trip.sh
content: #!/bin/bash
# Search for train connections between two stations
# Usage: ./trip.sh <from-station> <to-station> [date] [time] [num-results]
#
# Date format: YYYYMMDD (default: today)
# Time format: HHMM (default: now)
# Example: ./trip.sh "Wien Hbf" "Salzburg Hbf" 20260109 0800 5

FROM="${1:-}"
TO="${2:-}"
DATE="${3:-$(date +%Y%m%d)}"
TIME="${4:-$(date +%H%M)}00"
NUM="${5:-5}"

if [ -z "$FROM" ] || [ -z "$TO" ]; then
    echo "Usage: $0 <from-station> <to-station> [date] [time] [num-results]"
    echo "Example: $0 'Wien Hbf' 'Salzburg Hbf' 20260109 0800 5"
    exit 1
fi

# Ensure time has seconds
if [ ${#TIME} -eq 4 ]; then
    TIME="${TIME}00"
fi

# First, resolve station names to LIDs
FROM_LID=$(curl -s -X POST "https://fahrplan.oebb.at/bin/mgate.exe" \
  -H "Content-Type: application/json" \
  -d '{
    "id":"1","ver":"1.67","lang":"deu",
    "auth":{"type":"AID","aid":"OWDL4fE4ixNiPBBm"},
    "client":{"id":"OEBB","type":"WEB","name":"webapp","l":"vs_webapp"},
    "formatted":false,
    "svcReqL":[{
      "req":{"input":{"field":"S","loc":{"name":"'"$FROM"'","type":"S"},"maxLoc":1}},
      "meth":"LocMatch"
    }]
  }' | jq -r '.svcResL[0].res.match.locL[0].lid // empty')

TO_LID=$(curl -s -X POST "https://fahrplan.oebb.at/bin/mgate.exe" \
  -H "Content-Type: application/json" \
  -d '{
    "id":"1","ver":"1.67","lang":"deu",
    "auth":{"type":"AID","aid":"OWDL4fE4ixNiPBBm"},
    "client":{"id":"OEBB","type":"WEB","name":"webapp","l":"vs_webapp"},
    "formatted":false,
    "svcReqL":[{
      "req":{"input":{"field":"S","loc":{"name":"'"$TO"'","type":"S"},"maxLoc":1}},
      "meth":"LocMatch"
    }]
  }' | jq -r '.svcResL[0].res.match.locL[0].lid // empty')

if [ -z "$FROM_LID" ]; then
    echo "Error: Could not find station: $FROM"
    exit 1
fi

if [ -z "$TO_LID" ]; then
    echo "Error: Could not find station: $TO"
    exit 1
fi

# Now search for trips
curl -s -X POST "https://fahrplan.oebb.at/bin/mgate.exe" \
  -H "Content-Type: application/json" \
  -d '{
    "id":"1","ver":"1.67","lang":"deu",
    "auth":{"type":"AID","aid":"OWDL4fE4ixNiPBBm"},
    "client":{"id":"OEBB","type":"WEB","name":"webapp","l":"vs_webapp"},
    "formatted":false,
    "svcReqL":[{
      "req":{
        "depLocL":[{"lid":"'"$FROM_LID"'","type":"S"}],
        "arrLocL":[{"lid":"'"$TO_LID"'","type":"S"}],
        "jnyFltrL":[{"type":"PROD","mode":"INC","value":"1023"}],
        "getPolyline":false,
        "getPasslist":true,
        "outDate":"'"$DATE"'",
        "outTime":"'"$TIME"'",
        "outFrwd":true,
        "numF":'"$NUM"'
      },
      "meth":"TripSearch"
    }]
  }' | jq '
    .svcResL[0].res as $res |
    $res.outConL[] | {
      date: .date,
      departure: {
        time: (.dep.dTimeS | "\(.[0:2]):\(.[2:4])"),
        timeReal: (if .dep.dTimeR then (.dep.dTimeR | "\(.[0:2]):\(.[2:4])") else null end),
        platform: .dep.dPltfS.txt,
        station: ($res.common.locL[.dep.locX].name)
      },
      arrival: {
        time: (.arr.aTimeS | "\(.[0:2]):\(.[2:4])"),
        timeReal: (if .arr.aTimeR then (.arr.aTimeR | "\(.[0:2]):\(.[2:4])") else null end),
        platform: .arr.aPltfS.txt,
        station: ($res.common.locL[.arr.locX].name)
      },
      duration: (.dur | "\(.[0:2])h \(.[2:4])m"),
      changes: .chg,
      legs: [.secL[] | select(.type == "JNY") | {
        train: ($res.common.prodL[.jny.prodX].name // "Train"),
        category: ($res.common.prodL[.jny.prodX].prodCtx.catOutL // ""),
        direction: .jny.dirTxt,
        departure: {
          time: (.dep.dTimeS | "\(.[0:2]):\(.[2:4])"),
          station: ($res.common.locL[.dep.locX].name),
          platform: .dep.dPltfS.txt
        },
        arrival: {
          time: (.arr.aTimeS | "\(.[0:2]):\(.[2:4])"),
          station: ($res.common.locL[.arr.locX].name),
          platform: .arr.aPltfS.txt
        }
      }]
    }
  '


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)