diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..66c3fba --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +out/ +.git/ +.jj/ + +.gitignore diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..daecc85 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +FROM python:3.13-slim AS base +# FROM ghcr.io/astral-sh/uv:0.8-debian-slim AS builder +# +# WORKDIR /app +# +# COPY pyproject.toml README.md uv.lock ./ +# +# RUN uv sync --frozen +# +# COPY . /app + +FROM base AS builder +COPY --from=ghcr.io/astral-sh/uv:0.8 /uv /bin/uv +ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy +WORKDIR /app +COPY uv.lock pyproject.toml /app/ +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-install-project --no-dev +COPY . /app +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-dev + +# TODO: Run as non-root user (but careful with output directory ownership) +FROM base AS runtime + +WORKDIR /app +ENV PATH="/app/.venv/bin:$PATH" + +COPY --from=builder /app /app + +ENTRYPOINT ["nightjet"] diff --git a/README.md b/README.md index f9f63c4..d430769 100644 --- a/README.md +++ b/README.md @@ -1,3551 +1,85 @@ -# Nightjet API +# Nightjetter -Most from: `https://martinlangbecker.github.io/night-train-apis/?urls.primaryName=NightJet#/Initialize/post_init_start` +A search through all the available prices for a single ÖBB Nightjet route from A to B. -`curl -X 'POST' 'https://www.nightjet.com/nj-booking-ocp/init/start' -H 'accept: application/json' -H 'Referer: https://www.nightjet.com/' -H 'Content-Type: application/json' -d '{ "lang": "de" }' > resp/init_resp.json` +Can be used to continuously monitor prices and notify the user on changes, +or for a single search for all available prices. -- start with init request to grab "token" key from response -- the the token (jwt) in connection search as 'x-token' header +Comes with a nice Python CLI, or could be imported as a Python library for programmatic use. -`eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NTU1MzcwMjUsInB1YmxpY0lkIjoiYWZiZjM3NzBjZGU2NDllNTg2ZGJiMWYyOTIxN2NkYWUifQ.FqZMBeORf1SvXzHBOgwvPoTi7xPhbq4tbIg8KMSX5eI` +## The CLI -## connection request +It comes with a `typer`-supported CLI which you can use to craft your search. +Most options should be pretty self-explanatory, but a few notes: -WORKING request +- `monitor-mode` switches between a single search or the app running continuously until you turn it off. +- Any files created by the application are put under the `base-output-directory`, so make sure it has write permissions there. +- I am not sure how important the `birthdate` is, but included it since Nightjet may give discounts for young people or seniors? +- You can use the price snapshots, if you decide to create them with `dump-price-snapshot`, to create pretty time series graphs +- Currently, we _only_ support `ntfy`, as well as the official `ntfy` server as the notification channel -```bash -curl 'https://www.nightjet.com/nj-booking-ocp/connection/8096003/8796001/2025-10-14' \ - -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:139.0) Gecko/20100101 Firefox/139.0' \ - -H 'Accept: application/json' \ - -H 'Accept-Language: en-US,en;q=0.5' \ - -H 'Accept-Encoding: gzip, deflate, br, zstd' \ - -H 'Referer: https://www.nightjet.com/en/ticket-buchen/' \ - -H 'x-token: eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NTU1NDEwODUsInB1YmxpY0lkIjoiN2JjYTBhMGZlNjIyNDVlNGIyZWVlYjQzN2NkM2RjMzgifQ._1VxBxOcxQagYg7DpX-gQprU7i6GT7CBOb8SHixJf6I' \ - -H 'Connection: keep-alive' \ - -H 'Cookie: 34d5d3b7c2b81811ce5d8c490a20f30f=2c79d5b872ea8e0ebceca7def7e259d3; NSC_ESNS=21cebc26-33db-189a-9678-daf5b9e7e029_1676131999_2540214590_00000000000566328293' \ - -H 'Sec-Fetch-Dest: empty' \ - -H 'Sec-Fetch-Mode: cors' \ - -H 'Sec-Fetch-Site: same-origin' \ - -H 'DNT: 1' \ - -H 'Sec-GPC: 1' \ - -H 'Priority: u=4' \ - -H 'Pragma: no-cache' \ - -H 'Cache-Control: no-cache'``` +```help + Usage: nightjet [OPTIONS] TRAVEL_DATE -### connection response - -```json -{ - "connections": [ - { - "from": { - "name": "Berlin Hbf", - "number": "8011160" - }, - "to": { - "name": "Paris Est", - "number": "8700011" - }, - "trains": [ - { - "train": "NJ 40424", - "departure": { - "utc": 1760461680000, - "local": "2025-10-14T19:08:00" - }, - "arrival": { - "utc": 1760513880000, - "local": "2025-10-15T09:38:00" - }, - "trainType": "regular", - "seatAsIC": false - } - ] - }, - { - "from": { - "name": "Berlin Hbf", - "number": "8011160" - }, - "to": { - "name": "Paris Est", - "number": "8700011" - }, - "trains": [ - { - "train": "NJ 40424", - "departure": { - "utc": 1760634480000, - "local": "2025-10-16T19:08:00" - }, - "arrival": { - "utc": 1760686680000, - "local": "2025-10-17T09:38:00" - }, - "trainType": "regular", - "seatAsIC": false - } - ] - }, - { - "from": { - "name": "Berlin Hbf", - "number": "8011160" - }, - "to": { - "name": "Paris Est", - "number": "8700011" - }, - "trains": [ - { - "train": "NJ 40424", - "departure": { - "utc": 1760893680000, - "local": "2025-10-19T19:08:00" - }, - "arrival": { - "utc": 1760945880000, - "local": "2025-10-20T09:38:00" - }, - "trainType": "regular", - "seatAsIC": false - } - ] - } - ] -} +╭─ Arguments ─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ * travel_date TEXT Travel day to search from. (YYYY-MM-DD) [default: None] [required] │ +╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ --start-station INTEGER Departure station number. (default: Berlin Hbf) │ +│ [default: 8096003] │ +│ --end-station INTEGER Destination station number. (default: Paris Est) │ +│ [default: 8796001] │ +│ --birthdate TEXT Traveller birthdate, may be important for │ +│ discounts. (YYYY-MM-DD) │ +│ [default: 1990-01-01] │ +│ --notification-channel TEXT ntfy channel to inform user on. │ +│ [default: nightjet-price-notifier] │ +│ --monitor-mode --no-monitor-mode Run queries repeatedly over time. If False only │ +│ runs a single query (oneshot mode). │ +│ [default: monitor-mode] │ +│ --monitor-frequency INTEGER How often to run price queries if in monitoring │ +│ mode, in seconds. │ +│ [default: 3600] │ +│ --base-output-directory PATH Directory in which to output all result files. │ +│ [default: out] │ +│ --lowest-prices-filename TEXT Filename for collecting lowest found prices. │ +│ [default: lowest.csv] │ +│ --price-snapshot-pattern TEXT Filename pattern for saving all prices of each │ +│ query. Takes %%DATE%% as pattern to replace with │ +│ current unix timestamp. │ +│ [default: all_prices_%%DATE%%.csv] │ +│ --dump-price-snapshot --no-dump-price-snapshot Dump _all_ queried prices into a timestamped csv │ +│ file. │ +│ [default: dump-price-snapshot] │ +│ --install-completion Install completion for the current shell. │ +│ --show-completion Show completion for the current shell, to copy it │ +│ or customize the installation. │ +│ --help Show this message and exit. │ +╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ``` -## booking Request +## Future -example request body +This tool does (mostly did) what I needed it to do. +That means I don't have a big drive to work on, change and add to the current codebase. +Nevertheless, feel welcome to file an issue if you noticed something, or have a feature request. +PRs are of course also always welcome. -```json -{ - "njFrom": 8011160, - //"njArr": 1697307840000, - "njDep": 1760461680000, - "njTo": 8700011, - //"finalFrom": 8000199, - //"finalTo": 5696001, - "maxChanges": 0, - "filter": { - // "njTrain": "NJ 40491", - "njDeparture": 1760461680000, - // "njRoute": [ - // { - // "arrival": "2024-06-20T20:21:00+02:00", - // "arrivalAccessExitAmendable": true, - // "arrivalPlatform": "3", - // "departure": "2024-06-20T20:21:00+02:00", - // "departureAccessExitAmendable": true, - // "departurePlatform": "3", - // "departurePlatformRT": "3", - // "gpsCoordinates": [ - // 53455910, - // 9991699 - // ], - // "name": "Hamburg-Harburg", - // "stationNumber": 8000147, - // "stopState": null - // } - // ], - // "njProduct": "ARES Sparschiene Nachtverkehr", - // "privateVariationCount": null - }, - "objects": [ - { - "birthDate": "1993-10-13", - "cards": [ - 127 - ], - "gender": "female", - "type": "person" - } - ], - "relations": [ - { - "lhs": 1, - "relationType": "attendant_of", - "rhs": 0 - } - ], - "lang": "de" -} -``` +Here are a bunch of things I think should definitely be considered for further work: -WORKING request body: +1. The code is not very organized, and everything is one big file. + Perhaps this could be extracted into the individual areas of operation a bit better. -```bash -curl 'https://www.nightjet.com/nj-booking-ocp/offer/get' \ - --compressed \ - -X POST \ - -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:139.0) Gecko/20100101 Firefox/139.0' \ - -H 'Accept: application/json' \ - -H 'Accept-Language: en-US,en;q=0.5' \ - -H 'Accept-Encoding: gzip, deflate, br, zstd' \ - -H 'Referer: https://www.nightjet.com/en/ticket-buchen/' \ - -H 'content-type: application/json' \ - -H 'x-token: eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NTU1NDEwODUsInB1YmxpY0lkIjoiN2JjYTBhMGZlNjIyNDVlNGIyZWVlYjQzN2NkM2RjMzgifQ._1VxBxOcxQagYg7DpX-gQprU7i6GT7CBOb8SHixJf6I' \ - -H 'Origin: https://www.nightjet.com' \ - -H 'Connection: keep-alive' \ - -H 'Cookie: 34d5d3b7c2b81811ce5d8c490a20f30f=2c79d5b872ea8e0ebceca7def7e259d3; NSC_ESNS=21b97221-349f-189a-9678-daf5b9e7e029_1525529053_2925697282_00000000013450580068' \ - -H 'Sec-Fetch-Dest: empty' \ - -H 'Sec-Fetch-Mode: cors' \ - -H 'Sec-Fetch-Site: same-origin' \ - -H 'DNT: 1' \ - -H 'Sec-GPC: 1' \ - -H 'Priority: u=4' \ - --data-raw '{"njFrom":8011160,"njDep":1760461680000,"njTo":8700011,"maxChanges":0,"connections":1,"filter":{"njTrain":"NJ 40424","njDeparture":1760461680000},"objects":[{"type":"person","birthDate":"1995-08-11","cards":[]}],"relations":[],"lang":"en"}' -``` +2. Additional patterns for the price snapshots. We currently only consider `%%DATE%%` as a valid substitution. + It is substituted into a unix timestamp. More flexibility here could be nice. + Perhaps also allow the substitution in other paths passed to the cli. -request data +3. The stations are only considered as internal numbers (`int` types). This would be a first QoL change, + to automatically look up the correct station number for whatever is passed in. -```json -{ - "njFrom": 8011160, - "njDep": 1760461680000, - "njTo": 8700011, - "maxChanges": 0, - "connections": 1, - "filter": { - "njTrain": "NJ 40424", - "njDeparture": 1760461680000 - }, - "objects": [ - { - "type": "person", - "birthDate": "1995-08-11", - "cards": [] - } - ], - "relations": [], - "lang": "en" -} -``` +4. We don't map the trains, stations and journeys as internal objects. + Instead they are all passed around as dicts and JSON objects. + This could definitely be improved for better maintainability. -### booking response - -```json -{ - "result": [ - { - "connections": [ - { - "sections": [ - { - "departureTimestamp": "2025-10-14T19:08:00+02:00", - "arrivalTimestamp": "2025-10-15T09:38:00+02:00", - "passlist": [ - { - "name": "Berlin Hbf", - "stationNumber": 8011160, - "departure": "2025-10-14T19:08:00+02:00", - "departureAccessExitAmendable": true, - "departurePlatform": "13", - "gpsCoordinates": [ - 52525589, - 13369549 - ] - }, - { - "name": "Göttingen", - "stationNumber": 8000128, - "departure": "2025-10-14T21:37:00+02:00", - "arrival": "2025-10-14T21:35:00+02:00", - "arrivalAccessExitAmendable": true, - "arrivalPlatform": "10", - "departureAccessExitAmendable": true, - "departurePlatform": "10", - "gpsCoordinates": [ - 51536812, - 9926069 - ] - }, - { - "name": "Kassel-Wilhelmshöhe", - "stationNumber": 8003200, - "departure": "2025-10-14T22:08:00+02:00", - "arrival": "2025-10-14T21:58:00+02:00", - "arrivalAccessExitAmendable": true, - "arrivalPlatform": "1", - "departureAccessExitAmendable": true, - "departurePlatform": "1", - "gpsCoordinates": [ - 51312558, - 9447114 - ] - }, - { - "name": "Frankfurt(Main)Süd", - "stationNumber": 8002041, - "departure": "2025-10-15T00:26:00+02:00", - "arrival": "2025-10-15T00:22:00+02:00", - "arrivalAccessExitAmendable": true, - "arrivalPlatform": "6", - "departureAccessExitAmendable": true, - "departurePlatform": "6", - "gpsCoordinates": [ - 50099365, - 8686456 - ] - }, - { - "name": "Mannheim Hbf", - "stationNumber": 8000244, - "departure": "2025-10-15T03:40:00+02:00", - "arrival": "2025-10-15T01:34:00+02:00", - "arrivalAccessExitAmendable": false, - "departureAccessExitAmendable": false, - "gpsCoordinates": [ - 49479352, - 8468917 - ] - }, - { - "name": "Forbach Frontière de l'État", - "stationNumber": 8002021, - "departure": "2025-10-15T05:11:00+02:00", - "arrival": "2025-10-15T05:11:00+02:00", - "arrivalAccessExitAmendable": false, - "departureAccessExitAmendable": false, - "gpsCoordinates": [ - 49214089, - 6944311 - ] - }, - { - "name": "Paris Est", - "stationNumber": 8700011, - "arrival": "2025-10-15T09:38:00+02:00", - "arrivalAccessExitAmendable": true, - "stopState": [ - "ARR_PROGNOSED" - ], - "gpsCoordinates": [ - 48876976, - 2359120 - ] - } - ], - "attributes": [ - { - "attributeType": "operator", - "range": [ - 0, - 6 - ], - "value": "DPN", - "svalue": "DPN", - "lvalue": "Nahreisezug" - }, - { - "attributeType": "operator_id", - "range": [ - 0, - 6 - ], - "value": "81" - }, - { - "attributeType": "pclass", - "range": [ - 0, - 6 - ], - "value": 3 - }, - { - "attributeType": "intcat", - "range": [ - 0, - 6 - ], - "value": "NJ" - }, - { - "attributeType": "cat", - "range": [ - 0, - 6 - ], - "value": "NJ ", - "svalue": "NJ", - "lvalue": "nightjet", - "code": "3" - }, - { - "attributeType": "name", - "range": [ - 0, - 6 - ], - "value": "NJ 40424" - }, - { - "attributeType": "num", - "range": [ - 0, - 6 - ], - "value": "40424" - }, - { - "attributeType": "intnum", - "range": [ - 0, - 6 - ], - "value": 40424 - }, - { - "attributeType": "generic", - "range": [ - 0, - 6 - ], - "value": "Subject to compulsory reservation", - "code": "RP", - "priority": 1 - }, - { - "attributeType": "generic", - "range": [ - 0, - 6 - ], - "value": "space for wheelchairs", - "code": "RO", - "priority": 150 - }, - { - "attributeType": "generic", - "range": [ - 0, - 6 - ], - "value": "Wheelchair space - For advance notification, call +43 5 1717", - "code": "OA", - "priority": 150 - }, - { - "attributeType": "generic", - "range": [ - 0, - 6 - ], - "value": "WC accessible for wheelchair", - "code": "OC", - "priority": 150 - }, - { - "attributeType": "generic", - "range": [ - 0, - 6 - ], - "value": "Bicycles conveyed - subject to reservation", - "code": "FR", - "priority": 250 - }, - { - "attributeType": "generic", - "range": [ - 0, - 6 - ], - "value": "Number of bicycles conveyed limited", - "code": "FK", - "priority": 250 - }, - { - "attributeType": "generic", - "range": [ - 0, - 6 - ], - "value": "2nd class only seated accommodation", - "code": "J2", - "priority": 300 - }, - { - "attributeType": "generic", - "range": [ - 0, - 6 - ], - "value": "ÖBB Nightjet (www.nightjet.com)", - "code": "OJ", - "priority": 320 - }, - { - "attributeType": "generic", - "range": [ - 0, - 6 - ], - "value": "IRT Integrated Reservation Ticket", - "code": "CT", - "priority": 320 - }, - { - "attributeType": "generic", - "range": [ - 0, - 6 - ], - "value": "Global price", - "code": "GP", - "priority": 320 - }, - { - "attributeType": "generic", - "range": [ - 0, - 6 - ], - "value": "Sleeping-car", - "code": "SW", - "priority": 400 - }, - { - "attributeType": "generic", - "range": [ - 0, - 6 - ], - "value": "Couchettes", - "code": "LW", - "priority": 400 - }, - { - "attributeType": "generic", - "range": [ - 0, - 6 - ], - "value": "snacks and beverages available from sleeper/couchette attendant", - "code": "MN", - "priority": 450 - }, - { - "attributeType": "generic", - "range": [ - 0, - 6 - ], - "value": "Timetable is subject to change or adjustment", - "code": "50", - "priority": 921 - }, - { - "attributeType": "generic", - "range": [ - 0, - 6 - ], - "value": "Hinweis: Fahrzeiten können sich noch ändern.", - "code": "s1", - "priority": 921 - }, - { - "attributeType": "generic", - "range": [ - 0, - 6 - ], - "value": "nightjet", - "code": "ZN", - "priority": 100 - }, - { - "attributeType": "info", - "range": [ - 0, - 6 - ], - "value": "Paris Est", - "code": "RL" - }, - { - "attributeType": "dir", - "range": [ - 0, - 6 - ], - "value": "Paris Est" - } - ], - "i": 0, - "transportType": "journey" - } - ], - "offers": [ - { - "name": "Standard-Ticket inkl. Reservierung", - "productType": "CONNECTION", - "partialOffer": false, - "productDetails": [ - { - "scope": [ - [ - 0, - 0, - 0, - 6 - ] - ], - "title": "ARES Standard-Ticket Nachtverkehr", - "name": { - "de": "Standard-Ticket inkl. Reservierung", - "en": "Standard-Ticket incl. Reservation", - "it": "Standard-Ticket prenotazione incl." - }, - "validityPeriodFrom": "2025-10-14T19:08:00+02:00", - "validityPeriodTo": "2025-10-15T09:38:00+02:00", - "globallyPriced": true, - "objects": [ - { - "index": 0, - "priceClass2": 89.9 - } - ], - "prodGroupLabels": [ - "918 1 Reservierung Default", - "Vollstorno", - "Zugbindung", - "eineFahrt", - "featured" - ], - "validityType": "oneway" - } - ], - "reservation": { - "reservationSegments": [ - { - "scope": [ - 0, - 0, - 0, - 6 - ], - "compartments": [ - { - "externalIdentifier": "couchette4", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "objects": [ - { - "index": 0, - "price": 154.9, - "reservability": "RP" - } - ], - "id": 3756464, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 4 Personen", - "en": "Compartment for 4 passengers", - "fr": ".", - "it": "scompartimento per 4 persone" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 65, - "capacity": 4 - }, - { - "externalIdentifier": "couchette6", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "objects": [ - { - "index": 0, - "price": 134.9, - "reservability": "RP" - } - ], - "id": 3756468, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 6 Personen", - "en": "Compartment for 6 passengers", - "fr": ".", - "it": "Scompartimento per 6 persone" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "in der Mitte", - "en": "middle", - "fr": "in der Mitte", - "it": "preferibilmente centrale" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 45, - "capacity": 6 - }, - { - "externalIdentifier": "singlePlus", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 664.9, - "reservability": "RP" - } - ], - "id": 3756876, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 1 Person mit Dusche/WC (Single plus)", - "en": "Compartment for 1 passenger with shower/WC (Single plus)", - "fr": ".", - "it": "Scompartimento per 1 person1 con doccia/WC (Single plus)" - }, - "spotLocations": [], - "surchargePrice": 575, - "capacity": 1 - }, - { - "externalIdentifier": "privateCouchette", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "privateVariations": [ - { - "count": 1, - "allocations": [ - { - "objects": [ - { - "index": 0, - "price": 564.9 - } - ] - } - ], - "surchargePrice": 475 - } - ], - "id": 5000057, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Privatabteil im Liegewagen für bis zu 6 Personen", - "en": "Private compartment for up to 6 passengers in a couchette coach", - "fr": ".", - "it": "Scompartimento privato in carrozza cuccette per 6 passeggeri al massimo" - }, - "spotLocations": [], - "capacity": 6, - "privateCompartmentContainsSubcompartments": false - }, - { - "externalIdentifier": "privateSeat", - "freeFollowupReservationApplied": false, - "accommodationType": "SE", - "special": false, - "privateVariations": [ - { - "count": 1, - "allocations": [ - { - "objects": [ - { - "index": 0, - "price": 424.9 - } - ] - } - ], - "surchargePrice": 335 - } - ], - "id": 5000059, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Privatabteil im Sitzwagen 2. Klasse", - "en": "Private compartment in a 2nd class seated coach", - "fr": ".", - "it": "Scompartimento privato in carrozza con posti a sedere, 2a classe" - }, - "spotLocations": [], - "capacity": 6, - "privateCompartmentContainsSubcompartments": false - }, - { - "externalIdentifier": "femaleCouchette4", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "objects": [ - { - "index": 0, - "price": 154.9, - "reservability": "RP" - } - ], - "id": 5000068, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil für 4 Personen", - "en": "Ladies only compartment for 4 passengers", - "fr": ".", - "it": "Scompartimento donna per 4 persone" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente finestrinopreferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 65, - "capacity": 4 - }, - { - "externalIdentifier": "femaleCouchette6", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "objects": [ - { - "index": 0, - "price": 134.9, - "reservability": "RP" - } - ], - "id": 5000069, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil für 6 Personen", - "en": "Ladies only compartment for 6 passengers", - "fr": ".", - "it": "Scompartimento donna per 6 persone" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente finestrinopreferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "in der Mitte", - "en": "middle", - "fr": "in der Mitte", - "it": "preferibilmente centrale" - } - } - ], - "surchargePrice": 45, - "capacity": 6 - }, - { - "externalIdentifier": "sideCorridorCoach_2", - "freeFollowupReservationApplied": false, - "accommodationType": "SE", - "special": false, - "objects": [ - { - "index": 0, - "price": 89.9, - "reservability": "RP" - } - ], - "id": 5000075, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Sitzplatz 2. Klasse", - "en": "Seat 2nd class", - "fr": ".", - "it": "Posto a sedere in 2a classe" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "möglichst am Fenster", - "en": "if possible at the window", - "fr": "möglichst am Fenster", - "it": "preferibilmente finestrino" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "möglichst in der Mitte", - "en": "if possible in the center", - "fr": "möglichst in der Mitte", - "it": "preferibilmente centrale" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "möglichst am Gang", - "en": "if possible at the corridor", - "fr": "möglichst am Gang", - "it": "preferibilmente corridoio" - } - }, - { - "param": "NECESSARILY_WINDOW_LOWER", - "name": { - "de": "unbedingt am Fenster", - "en": "absolutely situated at the window", - "fr": "unbedingt am Fenster", - "it": "assolutamente finestrino" - } - } - ], - "capacity": 6 - }, - { - "externalIdentifier": "privateCouchette4comfort", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "privateVariations": [ - { - "count": 1, - "allocations": [ - { - "objects": [ - { - "index": 0, - "price": 564.9 - } - ] - } - ], - "surchargePrice": 475 - } - ], - "id": 5000085, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Privatabteil comfort im Liegewagen für bis zu 4 Personen", - "en": "Comfort private compartment in a couchette coach for up to 4 passengers", - "fr": ".", - "it": "Scompartimento privato comfort per 4 persone al massimo" - }, - "spotLocations": [], - "capacity": 4, - "privateCompartmentContainsSubcompartments": false - }, - { - "externalIdentifier": "couchette4comfort", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "objects": [ - { - "index": 0, - "price": 154.9, - "reservability": "RP" - } - ], - "id": 5000107, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil comfort für 4 Personen", - "en": "Comfort compartment for 4 passengers", - "fr": ".", - "it": "Scompartimento comfort per 4 persone" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 65, - "capacity": 4 - }, - { - "externalIdentifier": "femaleCouchette4comfort", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "objects": [ - { - "index": 0, - "price": 154.9, - "reservability": "RP" - } - ], - "id": 5000108, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil comfort für 4 Personen", - "en": "Comfort ladies only compartment for 4 passengers", - "fr": ".", - "it": "Scompartimento donna comfort per 4 persone" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 65, - "capacity": 4 - }, - { - "externalIdentifier": "femaleT3Plus", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 264.9, - "reservability": "RP" - } - ], - "id": 5000151, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil für 3 Personen mit Dusche/WC (Triple plus)", - "en": "Ladies only compartment for 3 passengers with shower/WC (Triple plus)", - "fr": ".", - "it": "Scompartimento donna per 3 persone con doccia/WC (Triple plus)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "in der Mitte", - "en": "middle", - "fr": "in der Mitte", - "it": "preferibilmente centrale" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 175, - "capacity": 3 - }, - { - "externalIdentifier": "mixedT3Plus", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 264.9, - "reservability": "RP" - } - ], - "id": 5000153, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 3 Personen mit Dusche/WC (Triple plus)", - "en": "Compartment for 3 passengers with shower/WC (Triple plus)", - "fr": ".", - "it": "Scompartimento per 3 persone con doccia/WC (Triple plus)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "in der Mitte", - "en": "middle", - "fr": "in der Mitte", - "it": "preferibilmente centrale" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 175, - "capacity": 3 - }, - { - "externalIdentifier": "mixedDoublePlus", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 329.9, - "reservability": "RP" - } - ], - "id": 5000160, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 2 Personen mit Dusche/WC (Double plus)", - "en": "Compartment for 2 passengers with shower/WC (Double plus)", - "fr": ".", - "it": "Scompartimento per 2 persone con doccia/WC (Double plus)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 240, - "capacity": 2 - }, - { - "externalIdentifier": "femaleDoublePlus", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 329.9, - "reservability": "RP" - } - ], - "id": 5000162, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil für 2 Personen mit Dusche/WC (Double plus)", - "en": "Ladies only compartment for 2 passengers with shower/WC (Double plus)", - "fr": ".", - "it": "Scompartimento donna per 2 persone con doccia/WC (Double plus)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 240, - "capacity": 2 - }, - { - "externalIdentifier": "single", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 514.9, - "reservability": "RP" - } - ], - "id": 5000198, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 1 Person (Single)", - "en": "Compartment for 1 passenger (Single)", - "fr": ".", - "it": "Scompartimento per 1 persona (Single)" - }, - "spotLocations": [], - "surchargePrice": 425, - "capacity": 1 - }, - { - "externalIdentifier": "femaleDouble", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 259.9, - "reservability": "RP" - } - ], - "id": 5000200, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil für 2 Personen (Double)", - "en": "Ladies only compartment for 2 passengers (Double)", - "fr": ".", - "it": "Scompartimento donna per 2 persone (Double)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 170, - "capacity": 2 - }, - { - "externalIdentifier": "mixedDouble", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 259.9, - "reservability": "RP" - } - ], - "id": 5000201, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 2 Personen (Double)", - "en": "Compartment for 2 passengers (Double)", - "fr": ".", - "it": "Scompartimento per 2 persone (Double)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 170, - "capacity": 2 - }, - { - "externalIdentifier": "femaleT3", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 204.9, - "reservability": "RP" - } - ], - "id": 5000202, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil für 3 Personen (Triple)", - "en": "Ladies only compartment for 3 passengers (Triple)", - "fr": ".", - "it": "Scompartimento donna per 3 persone (Triple)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "in der Mitte", - "en": "middle", - "fr": "in der Mitte", - "it": "preferibilmente centrale" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 115, - "capacity": 3 - }, - { - "externalIdentifier": "mixedT3", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 204.9, - "reservability": "RP" - } - ], - "id": 5000204, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 3 Personen (Triple)", - "en": "Compartment for 3 passengers (Triple)", - "fr": ".", - "it": "Scompartimento per 3 persone (Triple)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "in der Mitte", - "en": "middle", - "fr": "in der Mitte", - "it": "preferibilmente centrale" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 115, - "capacity": 3 - } - ], - "reservability": "RP" - } - ], - "priceClass2": 0 - }, - "priceClass2": 89.9, - "refCards": [], - "co2Savings": 252.89, - "outputTypes": [ - "MOBILE", - "PDF", - "SECURITY_PAPER" - ], - "prodGroupLabels": [ - "918 1 Reservierung Default", - "Vollstorno", - "Zugbindung", - "eineFahrt", - "featured" - ], - "validityPeriodFrom": "2025-10-14T19:08:00.000+02:00", - "validityPeriodTo": "2025-10-15T09:38:00.000+02:00", - "validityType": "oneway", - "rideBound": true, - "singleVariety": true, - "reservationMandatory": true - }, - { - "name": "Sparschiene Komfort inkl. Reservierung", - "productType": "CONNECTION", - "partialOffer": false, - "productDetails": [ - { - "scope": [ - [ - 0, - 0, - 0, - 6 - ] - ], - "title": "ARES Sparschiene Komfort Nachtverkehr", - "name": { - "de": "Sparschiene Komfort inkl. Reservierung", - "en": "Sparschiene Komfort incl. Reservation", - "it": "Sparschiene Komfort prenotazione incl." - }, - "validityPeriodFrom": "2025-10-14T19:08:00+02:00", - "validityPeriodTo": "2025-10-15T09:38:00+02:00", - "globallyPriced": true, - "objects": [ - { - "index": 0, - "priceClass2": 64.9 - } - ], - "prodGroupLabels": [ - "Zugbindung", - "eineFahrt", - "featured", - "komfortticketStorno" - ], - "validityType": "oneway" - } - ], - "reservation": { - "reservationSegments": [ - { - "scope": [ - 0, - 0, - 0, - 6 - ], - "compartments": [ - { - "externalIdentifier": "couchette4", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "objects": [ - { - "index": 0, - "price": 129.9, - "reservability": "RP" - } - ], - "id": 3756464, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 4 Personen", - "en": "Compartment for 4 passengers", - "fr": ".", - "it": "scompartimento per 4 persone" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 65, - "capacity": 4 - }, - { - "externalIdentifier": "couchette6", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "objects": [ - { - "index": 0, - "price": 109.9, - "reservability": "RP" - } - ], - "id": 3756468, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 6 Personen", - "en": "Compartment for 6 passengers", - "fr": ".", - "it": "Scompartimento per 6 persone" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "in der Mitte", - "en": "middle", - "fr": "in der Mitte", - "it": "preferibilmente centrale" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 45, - "capacity": 6 - }, - { - "externalIdentifier": "singlePlus", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 494.9, - "reservability": "RP" - } - ], - "id": 3756876, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 1 Person mit Dusche/WC (Single plus)", - "en": "Compartment for 1 passenger with shower/WC (Single plus)", - "fr": ".", - "it": "Scompartimento per 1 person1 con doccia/WC (Single plus)" - }, - "spotLocations": [], - "surchargePrice": 430, - "capacity": 1 - }, - { - "externalIdentifier": "privateCouchette", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "privateVariations": [ - { - "count": 1, - "allocations": [ - { - "objects": [ - { - "index": 0, - "price": 519.9 - } - ] - } - ], - "surchargePrice": 455 - } - ], - "id": 5000057, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Privatabteil im Liegewagen für bis zu 6 Personen", - "en": "Private compartment for up to 6 passengers in a couchette coach", - "fr": ".", - "it": "Scompartimento privato in carrozza cuccette per 6 passeggeri al massimo" - }, - "spotLocations": [], - "capacity": 6, - "privateCompartmentContainsSubcompartments": false - }, - { - "externalIdentifier": "privateSeat", - "freeFollowupReservationApplied": false, - "accommodationType": "SE", - "special": false, - "privateVariations": [ - { - "count": 1, - "allocations": [ - { - "objects": [ - { - "index": 0, - "price": 319.9 - } - ] - } - ], - "surchargePrice": 255 - } - ], - "id": 5000059, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Privatabteil im Sitzwagen 2. Klasse", - "en": "Private compartment in a 2nd class seated coach", - "fr": ".", - "it": "Scompartimento privato in carrozza con posti a sedere, 2a classe" - }, - "spotLocations": [], - "capacity": 6, - "privateCompartmentContainsSubcompartments": false - }, - { - "externalIdentifier": "femaleCouchette4", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "objects": [ - { - "index": 0, - "price": 129.9, - "reservability": "RP" - } - ], - "id": 5000068, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil für 4 Personen", - "en": "Ladies only compartment for 4 passengers", - "fr": ".", - "it": "Scompartimento donna per 4 persone" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente finestrinopreferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 65, - "capacity": 4 - }, - { - "externalIdentifier": "femaleCouchette6", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "objects": [ - { - "index": 0, - "price": 109.9, - "reservability": "RP" - } - ], - "id": 5000069, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil für 6 Personen", - "en": "Ladies only compartment for 6 passengers", - "fr": ".", - "it": "Scompartimento donna per 6 persone" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente finestrinopreferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "in der Mitte", - "en": "middle", - "fr": "in der Mitte", - "it": "preferibilmente centrale" - } - } - ], - "surchargePrice": 45, - "capacity": 6 - }, - { - "externalIdentifier": "sideCorridorCoach_2", - "freeFollowupReservationApplied": false, - "accommodationType": "SE", - "special": false, - "objects": [ - { - "index": 0, - "price": 64.9, - "reservability": "RP" - } - ], - "id": 5000075, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Sitzplatz 2. Klasse", - "en": "Seat 2nd class", - "fr": ".", - "it": "Posto a sedere in 2a classe" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "möglichst am Fenster", - "en": "if possible at the window", - "fr": "möglichst am Fenster", - "it": "preferibilmente finestrino" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "möglichst in der Mitte", - "en": "if possible in the center", - "fr": "möglichst in der Mitte", - "it": "preferibilmente centrale" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "möglichst am Gang", - "en": "if possible at the corridor", - "fr": "möglichst am Gang", - "it": "preferibilmente corridoio" - } - }, - { - "param": "NECESSARILY_WINDOW_LOWER", - "name": { - "de": "unbedingt am Fenster", - "en": "absolutely situated at the window", - "fr": "unbedingt am Fenster", - "it": "assolutamente finestrino" - } - } - ], - "capacity": 6 - }, - { - "externalIdentifier": "privateCouchette4comfort", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "privateVariations": [ - { - "count": 1, - "allocations": [ - { - "objects": [ - { - "index": 0, - "price": 519.9 - } - ] - } - ], - "surchargePrice": 455 - } - ], - "id": 5000085, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Privatabteil comfort im Liegewagen für bis zu 4 Personen", - "en": "Comfort private compartment in a couchette coach for up to 4 passengers", - "fr": ".", - "it": "Scompartimento privato comfort per 4 persone al massimo" - }, - "spotLocations": [], - "capacity": 4, - "privateCompartmentContainsSubcompartments": false - }, - { - "externalIdentifier": "couchette4comfort", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "objects": [ - { - "index": 0, - "price": 129.9, - "reservability": "RP" - } - ], - "id": 5000107, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil comfort für 4 Personen", - "en": "Comfort compartment for 4 passengers", - "fr": ".", - "it": "Scompartimento comfort per 4 persone" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 65, - "capacity": 4 - }, - { - "externalIdentifier": "femaleCouchette4comfort", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "objects": [ - { - "index": 0, - "price": 129.9, - "reservability": "RP" - } - ], - "id": 5000108, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil comfort für 4 Personen", - "en": "Comfort ladies only compartment for 4 passengers", - "fr": ".", - "it": "Scompartimento donna comfort per 4 persone" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 65, - "capacity": 4 - }, - { - "externalIdentifier": "femaleT3Plus", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 199.9, - "reservability": "RP" - } - ], - "id": 5000151, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil für 3 Personen mit Dusche/WC (Triple plus)", - "en": "Ladies only compartment for 3 passengers with shower/WC (Triple plus)", - "fr": ".", - "it": "Scompartimento donna per 3 persone con doccia/WC (Triple plus)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "in der Mitte", - "en": "middle", - "fr": "in der Mitte", - "it": "preferibilmente centrale" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 135, - "capacity": 3 - }, - { - "externalIdentifier": "mixedT3Plus", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 199.9, - "reservability": "RP" - } - ], - "id": 5000153, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 3 Personen mit Dusche/WC (Triple plus)", - "en": "Compartment for 3 passengers with shower/WC (Triple plus)", - "fr": ".", - "it": "Scompartimento per 3 persone con doccia/WC (Triple plus)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "in der Mitte", - "en": "middle", - "fr": "in der Mitte", - "it": "preferibilmente centrale" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 135, - "capacity": 3 - }, - { - "externalIdentifier": "mixedDoublePlus", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 249.9, - "reservability": "RP" - } - ], - "id": 5000160, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 2 Personen mit Dusche/WC (Double plus)", - "en": "Compartment for 2 passengers with shower/WC (Double plus)", - "fr": ".", - "it": "Scompartimento per 2 persone con doccia/WC (Double plus)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 185, - "capacity": 2 - }, - { - "externalIdentifier": "femaleDoublePlus", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 249.9, - "reservability": "RP" - } - ], - "id": 5000162, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil für 2 Personen mit Dusche/WC (Double plus)", - "en": "Ladies only compartment for 2 passengers with shower/WC (Double plus)", - "fr": ".", - "it": "Scompartimento donna per 2 persone con doccia/WC (Double plus)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 185, - "capacity": 2 - }, - { - "externalIdentifier": "single", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 384.9, - "reservability": "RP" - } - ], - "id": 5000198, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 1 Person (Single)", - "en": "Compartment for 1 passenger (Single)", - "fr": ".", - "it": "Scompartimento per 1 persona (Single)" - }, - "spotLocations": [], - "surchargePrice": 320, - "capacity": 1 - }, - { - "externalIdentifier": "femaleDouble", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 194.9, - "reservability": "RP" - } - ], - "id": 5000200, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil für 2 Personen (Double)", - "en": "Ladies only compartment for 2 passengers (Double)", - "fr": ".", - "it": "Scompartimento donna per 2 persone (Double)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 130, - "capacity": 2 - }, - { - "externalIdentifier": "mixedDouble", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 194.9, - "reservability": "RP" - } - ], - "id": 5000201, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 2 Personen (Double)", - "en": "Compartment for 2 passengers (Double)", - "fr": ".", - "it": "Scompartimento per 2 persone (Double)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 130, - "capacity": 2 - }, - { - "externalIdentifier": "femaleT3", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 169.9, - "reservability": "RP" - } - ], - "id": 5000202, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil für 3 Personen (Triple)", - "en": "Ladies only compartment for 3 passengers (Triple)", - "fr": ".", - "it": "Scompartimento donna per 3 persone (Triple)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "in der Mitte", - "en": "middle", - "fr": "in der Mitte", - "it": "preferibilmente centrale" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 105, - "capacity": 3 - }, - { - "externalIdentifier": "mixedT3", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 169.9, - "reservability": "RP" - } - ], - "id": 5000204, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 3 Personen (Triple)", - "en": "Compartment for 3 passengers (Triple)", - "fr": ".", - "it": "Scompartimento per 3 persone (Triple)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "in der Mitte", - "en": "middle", - "fr": "in der Mitte", - "it": "preferibilmente centrale" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 105, - "capacity": 3 - } - ], - "reservability": "RP" - } - ], - "priceClass2": 0 - }, - "priceClass2": 64.9, - "refCards": [], - "co2Savings": 252.89, - "outputTypes": [ - "MOBILE", - "PDF", - "SECURITY_PAPER" - ], - "prodGroupLabels": [ - "Zugbindung", - "eineFahrt", - "featured", - "komfortticketStorno" - ], - "validityPeriodFrom": "2025-10-14T19:08:00.000+02:00", - "validityPeriodTo": "2025-10-15T09:38:00.000+02:00", - "validityType": "oneway", - "rideBound": true, - "singleVariety": true, - "reservationMandatory": true - }, - { - "name": "Sparschiene inkl. Reservierung", - "productType": "CONNECTION", - "partialOffer": false, - "productDetails": [ - { - "scope": [ - [ - 0, - 0, - 0, - 6 - ] - ], - "title": "ARES Sparschiene Nachtverkehr", - "name": { - "de": "Sparschiene inkl. Reservierung", - "en": "Sparschiene incl. Reservation", - "it": "Sparschiene prenotazione incl." - }, - "validityPeriodFrom": "2025-10-14T19:08:00+02:00", - "validityPeriodTo": "2025-10-15T09:38:00+02:00", - "globallyPriced": true, - "objects": [ - { - "index": 0, - "priceClass2": 44.9 - } - ], - "prodGroupLabels": [ - "Kein Storno", - "Zugbindung", - "eineFahrt", - "featured", - "nightjetSparschiene" - ], - "validityType": "oneway" - } - ], - "reservation": { - "reservationSegments": [ - { - "scope": [ - 0, - 0, - 0, - 6 - ], - "compartments": [ - { - "externalIdentifier": "couchette4", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "objects": [ - { - "index": 0, - "price": 114.9, - "reservability": "RP" - } - ], - "id": 3756464, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 4 Personen", - "en": "Compartment for 4 passengers", - "fr": ".", - "it": "scompartimento per 4 persone" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 70, - "capacity": 4 - }, - { - "externalIdentifier": "couchette6", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "objects": [ - { - "index": 0, - "price": 99.9, - "reservability": "RP" - } - ], - "id": 3756468, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 6 Personen", - "en": "Compartment for 6 passengers", - "fr": ".", - "it": "Scompartimento per 6 persone" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "in der Mitte", - "en": "middle", - "fr": "in der Mitte", - "it": "preferibilmente centrale" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 55, - "capacity": 6 - }, - { - "externalIdentifier": "singlePlus", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 349.9, - "reservability": "RP" - } - ], - "id": 3756876, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 1 Person mit Dusche/WC (Single plus)", - "en": "Compartment for 1 passenger with shower/WC (Single plus)", - "fr": ".", - "it": "Scompartimento per 1 person1 con doccia/WC (Single plus)" - }, - "spotLocations": [], - "surchargePrice": 305, - "capacity": 1 - }, - { - "externalIdentifier": "privateCouchette", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "privateVariations": [ - { - "count": 1, - "allocations": [ - { - "objects": [ - { - "index": 0, - "price": 494.9 - } - ] - } - ], - "surchargePrice": 450 - } - ], - "id": 5000057, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Privatabteil im Liegewagen für bis zu 6 Personen", - "en": "Private compartment for up to 6 passengers in a couchette coach", - "fr": ".", - "it": "Scompartimento privato in carrozza cuccette per 6 passeggeri al massimo" - }, - "spotLocations": [], - "capacity": 6, - "privateCompartmentContainsSubcompartments": false - }, - { - "externalIdentifier": "privateSeat", - "freeFollowupReservationApplied": false, - "accommodationType": "SE", - "special": false, - "privateVariations": [ - { - "count": 1, - "allocations": [ - { - "objects": [ - { - "index": 0, - "price": 224.9 - } - ] - } - ], - "surchargePrice": 180 - } - ], - "id": 5000059, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Privatabteil im Sitzwagen 2. Klasse", - "en": "Private compartment in a 2nd class seated coach", - "fr": ".", - "it": "Scompartimento privato in carrozza con posti a sedere, 2a classe" - }, - "spotLocations": [], - "capacity": 6, - "privateCompartmentContainsSubcompartments": false - }, - { - "externalIdentifier": "femaleCouchette4", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "objects": [ - { - "index": 0, - "price": 114.9, - "reservability": "RP" - } - ], - "id": 5000068, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil für 4 Personen", - "en": "Ladies only compartment for 4 passengers", - "fr": ".", - "it": "Scompartimento donna per 4 persone" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente finestrinopreferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 70, - "capacity": 4 - }, - { - "externalIdentifier": "femaleCouchette6", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "objects": [ - { - "index": 0, - "price": 99.9, - "reservability": "RP" - } - ], - "id": 5000069, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil für 6 Personen", - "en": "Ladies only compartment for 6 passengers", - "fr": ".", - "it": "Scompartimento donna per 6 persone" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente finestrinopreferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "in der Mitte", - "en": "middle", - "fr": "in der Mitte", - "it": "preferibilmente centrale" - } - } - ], - "surchargePrice": 55, - "capacity": 6 - }, - { - "externalIdentifier": "sideCorridorCoach_2", - "freeFollowupReservationApplied": false, - "accommodationType": "SE", - "special": false, - "objects": [ - { - "index": 0, - "price": 44.9, - "reservability": "RP" - } - ], - "id": 5000075, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Sitzplatz 2. Klasse", - "en": "Seat 2nd class", - "fr": ".", - "it": "Posto a sedere in 2a classe" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "möglichst am Fenster", - "en": "if possible at the window", - "fr": "möglichst am Fenster", - "it": "preferibilmente finestrino" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "möglichst in der Mitte", - "en": "if possible in the center", - "fr": "möglichst in der Mitte", - "it": "preferibilmente centrale" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "möglichst am Gang", - "en": "if possible at the corridor", - "fr": "möglichst am Gang", - "it": "preferibilmente corridoio" - } - }, - { - "param": "NECESSARILY_WINDOW_LOWER", - "name": { - "de": "unbedingt am Fenster", - "en": "absolutely situated at the window", - "fr": "unbedingt am Fenster", - "it": "assolutamente finestrino" - } - } - ], - "capacity": 6 - }, - { - "externalIdentifier": "privateCouchette4comfort", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "privateVariations": [ - { - "count": 1, - "allocations": [ - { - "objects": [ - { - "index": 0, - "price": 494.9 - } - ] - } - ], - "surchargePrice": 450 - } - ], - "id": 5000085, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Privatabteil comfort im Liegewagen für bis zu 4 Personen", - "en": "Comfort private compartment in a couchette coach for up to 4 passengers", - "fr": ".", - "it": "Scompartimento privato comfort per 4 persone al massimo" - }, - "spotLocations": [], - "capacity": 4, - "privateCompartmentContainsSubcompartments": false - }, - { - "externalIdentifier": "couchette4comfort", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "objects": [ - { - "index": 0, - "price": 114.9, - "reservability": "RP" - } - ], - "id": 5000107, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil comfort für 4 Personen", - "en": "Comfort compartment for 4 passengers", - "fr": ".", - "it": "Scompartimento comfort per 4 persone" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 70, - "capacity": 4 - }, - { - "externalIdentifier": "femaleCouchette4comfort", - "freeFollowupReservationApplied": false, - "accommodationType": "LE", - "special": false, - "objects": [ - { - "index": 0, - "price": 114.9, - "reservability": "RP" - } - ], - "id": 5000108, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil comfort für 4 Personen", - "en": "Comfort ladies only compartment for 4 passengers", - "fr": ".", - "it": "Scompartimento donna comfort per 4 persone" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 70, - "capacity": 4 - }, - { - "externalIdentifier": "femaleT3Plus", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 139.9, - "reservability": "RP" - } - ], - "id": 5000151, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil für 3 Personen mit Dusche/WC (Triple plus)", - "en": "Ladies only compartment for 3 passengers with shower/WC (Triple plus)", - "fr": ".", - "it": "Scompartimento donna per 3 persone con doccia/WC (Triple plus)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "in der Mitte", - "en": "middle", - "fr": "in der Mitte", - "it": "preferibilmente centrale" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 95, - "capacity": 3 - }, - { - "externalIdentifier": "mixedT3Plus", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 139.9, - "reservability": "RP" - } - ], - "id": 5000153, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 3 Personen mit Dusche/WC (Triple plus)", - "en": "Compartment for 3 passengers with shower/WC (Triple plus)", - "fr": ".", - "it": "Scompartimento per 3 persone con doccia/WC (Triple plus)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "in der Mitte", - "en": "middle", - "fr": "in der Mitte", - "it": "preferibilmente centrale" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 95, - "capacity": 3 - }, - { - "externalIdentifier": "mixedDoublePlus", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 174.9, - "reservability": "RP" - } - ], - "id": 5000160, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 2 Personen mit Dusche/WC (Double plus)", - "en": "Compartment for 2 passengers with shower/WC (Double plus)", - "fr": ".", - "it": "Scompartimento per 2 persone con doccia/WC (Double plus)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 130, - "capacity": 2 - }, - { - "externalIdentifier": "femaleDoublePlus", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 174.9, - "reservability": "RP" - } - ], - "id": 5000162, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil für 2 Personen mit Dusche/WC (Double plus)", - "en": "Ladies only compartment for 2 passengers with shower/WC (Double plus)", - "fr": ".", - "it": "Scompartimento donna per 2 persone con doccia/WC (Double plus)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 130, - "capacity": 2 - }, - { - "externalIdentifier": "single", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 334.9, - "reservability": "RP" - } - ], - "id": 5000198, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 1 Person (Single)", - "en": "Compartment for 1 passenger (Single)", - "fr": ".", - "it": "Scompartimento per 1 persona (Single)" - }, - "spotLocations": [], - "surchargePrice": 290, - "capacity": 1 - }, - { - "externalIdentifier": "femaleDouble", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 169.9, - "reservability": "RP" - } - ], - "id": 5000200, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil für 2 Personen (Double)", - "en": "Ladies only compartment for 2 passengers (Double)", - "fr": ".", - "it": "Scompartimento donna per 2 persone (Double)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 125, - "capacity": 2 - }, - { - "externalIdentifier": "mixedDouble", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 169.9, - "reservability": "RP" - } - ], - "id": 5000201, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 2 Personen (Double)", - "en": "Compartment for 2 passengers (Double)", - "fr": ".", - "it": "Scompartimento per 2 persone (Double)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 125, - "capacity": 2 - }, - { - "externalIdentifier": "femaleT3", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 154.9, - "reservability": "RP" - } - ], - "id": 5000202, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Damenabteil für 3 Personen (Triple)", - "en": "Ladies only compartment for 3 passengers (Triple)", - "fr": ".", - "it": "Scompartimento donna per 3 persone (Triple)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "in der Mitte", - "en": "middle", - "fr": "in der Mitte", - "it": "preferibilmente centrale" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 110, - "capacity": 3 - }, - { - "externalIdentifier": "mixedT3", - "freeFollowupReservationApplied": false, - "accommodationType": "BE", - "special": false, - "objects": [ - { - "index": 0, - "price": 154.9, - "reservability": "RP" - } - ], - "id": 5000204, - "accommodationClass": [ - "class2" - ], - "name": { - "de": "Abteil für 3 Personen (Triple)", - "en": "Compartment for 3 passengers (Triple)", - "fr": ".", - "it": "Scompartimento per 3 persone (Triple)" - }, - "spotLocations": [ - { - "param": "WINDOW_UPPER", - "name": { - "de": "oben", - "en": "top", - "fr": "oben", - "it": "preferibilmente in alto" - } - }, - { - "param": "MIDDLE", - "name": { - "de": "in der Mitte", - "en": "middle", - "fr": "in der Mitte", - "it": "preferibilmente centrale" - } - }, - { - "param": "AISLE_LOWER", - "name": { - "de": "unten", - "en": "lower", - "fr": "unten", - "it": "preferibilmente in basso" - } - } - ], - "surchargePrice": 110, - "capacity": 3 - } - ], - "reservability": "RP" - } - ], - "priceClass2": 0 - }, - "priceClass2": 44.9, - "refCards": [], - "co2Savings": 252.89, - "outputTypes": [ - "MOBILE", - "PDF", - "SECURITY_PAPER" - ], - "prodGroupLabels": [ - "Kein Storno", - "Zugbindung", - "eineFahrt", - "featured", - "nightjetSparschiene" - ], - "validityPeriodFrom": "2025-10-14T19:08:00.000+02:00", - "validityPeriodTo": "2025-10-15T09:38:00.000+02:00", - "validityType": "oneway", - "rideBound": true, - "singleVariety": true, - "reservationMandatory": true - } - ], - "informationMessages": [ - { - "category": 99, - "validFrom": "2025-10-15T09:38:00+02:00", - "validTo": "2025-10-14T19:08:00+02:00", - "sectionIndex": 0, - "header": "Please note – timetable subject to change", - "text": "
Due to construction work or other factors affecting our services, the departure and/or arrival times of this service may change even on the selected day. If you buy a ticket, we will send you an email or sms text message to notify you about any modifications. (Please also check your spam folder regularly).
If the departure time changes 60 minutes or more, a refund is also possible and free of charge for a Sparschiene saver ticket up to 15 days before the first day of validity.
", - "textPlain": "Due to construction work or other factors affecting our services, the departure and/or arrival times of this service may change even on the selected day. If you buy a ticket, we will send you an email or sms text message to notify you about any modifications. (Please also check your spam folder regularly).If the departure time changes 60 minutes or more, a refund is also possible and free of charge for a Sparschiene saver ticket up to 15 days before the first day of validity." - } - ], - "njIndex": 0 - } - ], - "filter": { - "maxTrainChanges": 0 - }, - "rfpIndex": 0 - } - ], - "ok": true, - "responseCode": 200 -} -``` - -## Plan - -- send get request to `init/start` -> save x-token -- set get request to `` +5. Support more notification channels. This should be self-explanatory, and could probably be pretty + easily be improved with a library like [apprise](https://github.com/caronc/apprise?tab=readme-ov-file#developer-api-usage). diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..4eaabc7 --- /dev/null +++ b/docs/API.md @@ -0,0 +1,3549 @@ +# Nightjet API + +Some notes on the nightjet api + +Most from: `https://martinlangbecker.github.io/night-train-apis/?urls.primaryName=NightJet#/Initialize/post_init_start` + +`curl -X 'POST' 'https://www.nightjet.com/nj-booking-ocp/init/start' -H 'accept: application/json' -H 'Referer: https://www.nightjet.com/' -H 'Content-Type: application/json' -d '{ "lang": "de" }' > resp/init_resp.json` + +- start with init request to grab "token" key from response +- the the token (jwt) in connection search as 'x-token' header + +`eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NTU1MzcwMjUsInB1YmxpY0lkIjoiYWZiZjM3NzBjZGU2NDllNTg2ZGJiMWYyOTIxN2NkYWUifQ.FqZMBeORf1SvXzHBOgwvPoTi7xPhbq4tbIg8KMSX5eI` + +## connection request + +WORKING request + +```bash +curl 'https://www.nightjet.com/nj-booking-ocp/connection/8096003/8796001/2025-10-14' \ + -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:139.0) Gecko/20100101 Firefox/139.0' \ + -H 'Accept: application/json' \ + -H 'Accept-Language: en-US,en;q=0.5' \ + -H 'Accept-Encoding: gzip, deflate, br, zstd' \ + -H 'Referer: https://www.nightjet.com/en/ticket-buchen/' \ + -H 'x-token: eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NTU1NDEwODUsInB1YmxpY0lkIjoiN2JjYTBhMGZlNjIyNDVlNGIyZWVlYjQzN2NkM2RjMzgifQ._1VxBxOcxQagYg7DpX-gQprU7i6GT7CBOb8SHixJf6I' \ + -H 'Connection: keep-alive' \ + -H 'Cookie: 34d5d3b7c2b81811ce5d8c490a20f30f=2c79d5b872ea8e0ebceca7def7e259d3; NSC_ESNS=21cebc26-33db-189a-9678-daf5b9e7e029_1676131999_2540214590_00000000000566328293' \ + -H 'Sec-Fetch-Dest: empty' \ + -H 'Sec-Fetch-Mode: cors' \ + -H 'Sec-Fetch-Site: same-origin' \ + -H 'DNT: 1' \ + -H 'Sec-GPC: 1' \ + -H 'Priority: u=4' \ + -H 'Pragma: no-cache' \ + -H 'Cache-Control: no-cache'``` + +### connection response + +```json +{ + "connections": [ + { + "from": { + "name": "Berlin Hbf", + "number": "8011160" + }, + "to": { + "name": "Paris Est", + "number": "8700011" + }, + "trains": [ + { + "train": "NJ 40424", + "departure": { + "utc": 1760461680000, + "local": "2025-10-14T19:08:00" + }, + "arrival": { + "utc": 1760513880000, + "local": "2025-10-15T09:38:00" + }, + "trainType": "regular", + "seatAsIC": false + } + ] + }, + { + "from": { + "name": "Berlin Hbf", + "number": "8011160" + }, + "to": { + "name": "Paris Est", + "number": "8700011" + }, + "trains": [ + { + "train": "NJ 40424", + "departure": { + "utc": 1760634480000, + "local": "2025-10-16T19:08:00" + }, + "arrival": { + "utc": 1760686680000, + "local": "2025-10-17T09:38:00" + }, + "trainType": "regular", + "seatAsIC": false + } + ] + }, + { + "from": { + "name": "Berlin Hbf", + "number": "8011160" + }, + "to": { + "name": "Paris Est", + "number": "8700011" + }, + "trains": [ + { + "train": "NJ 40424", + "departure": { + "utc": 1760893680000, + "local": "2025-10-19T19:08:00" + }, + "arrival": { + "utc": 1760945880000, + "local": "2025-10-20T09:38:00" + }, + "trainType": "regular", + "seatAsIC": false + } + ] + } + ] +} +``` + +## booking Request + +example request body + +```json +{ + "njFrom": 8011160, + //"njArr": 1697307840000, + "njDep": 1760461680000, + "njTo": 8700011, + //"finalFrom": 8000199, + //"finalTo": 5696001, + "maxChanges": 0, + "filter": { + // "njTrain": "NJ 40491", + "njDeparture": 1760461680000, + // "njRoute": [ + // { + // "arrival": "2024-06-20T20:21:00+02:00", + // "arrivalAccessExitAmendable": true, + // "arrivalPlatform": "3", + // "departure": "2024-06-20T20:21:00+02:00", + // "departureAccessExitAmendable": true, + // "departurePlatform": "3", + // "departurePlatformRT": "3", + // "gpsCoordinates": [ + // 53455910, + // 9991699 + // ], + // "name": "Hamburg-Harburg", + // "stationNumber": 8000147, + // "stopState": null + // } + // ], + // "njProduct": "ARES Sparschiene Nachtverkehr", + // "privateVariationCount": null + }, + "objects": [ + { + "birthDate": "1993-10-13", + "cards": [ + 127 + ], + "gender": "female", + "type": "person" + } + ], + "relations": [ + { + "lhs": 1, + "relationType": "attendant_of", + "rhs": 0 + } + ], + "lang": "de" +} +``` + +WORKING request body: + +```bash +curl 'https://www.nightjet.com/nj-booking-ocp/offer/get' \ + --compressed \ + -X POST \ + -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:139.0) Gecko/20100101 Firefox/139.0' \ + -H 'Accept: application/json' \ + -H 'Accept-Language: en-US,en;q=0.5' \ + -H 'Accept-Encoding: gzip, deflate, br, zstd' \ + -H 'Referer: https://www.nightjet.com/en/ticket-buchen/' \ + -H 'content-type: application/json' \ + -H 'x-token: eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NTU1NDEwODUsInB1YmxpY0lkIjoiN2JjYTBhMGZlNjIyNDVlNGIyZWVlYjQzN2NkM2RjMzgifQ._1VxBxOcxQagYg7DpX-gQprU7i6GT7CBOb8SHixJf6I' \ + -H 'Origin: https://www.nightjet.com' \ + -H 'Connection: keep-alive' \ + -H 'Cookie: 34d5d3b7c2b81811ce5d8c490a20f30f=2c79d5b872ea8e0ebceca7def7e259d3; NSC_ESNS=21b97221-349f-189a-9678-daf5b9e7e029_1525529053_2925697282_00000000013450580068' \ + -H 'Sec-Fetch-Dest: empty' \ + -H 'Sec-Fetch-Mode: cors' \ + -H 'Sec-Fetch-Site: same-origin' \ + -H 'DNT: 1' \ + -H 'Sec-GPC: 1' \ + -H 'Priority: u=4' \ + --data-raw '{"njFrom":8011160,"njDep":1760461680000,"njTo":8700011,"maxChanges":0,"connections":1,"filter":{"njTrain":"NJ 40424","njDeparture":1760461680000},"objects":[{"type":"person","birthDate":"1995-08-11","cards":[]}],"relations":[],"lang":"en"}' +``` + +request data + +```json +{ + "njFrom": 8011160, + "njDep": 1760461680000, + "njTo": 8700011, + "maxChanges": 0, + "connections": 1, + "filter": { + "njTrain": "NJ 40424", + "njDeparture": 1760461680000 + }, + "objects": [ + { + "type": "person", + "birthDate": "1995-08-11", + "cards": [] + } + ], + "relations": [], + "lang": "en" +} +``` + +### booking response + +```json +{ + "result": [ + { + "connections": [ + { + "sections": [ + { + "departureTimestamp": "2025-10-14T19:08:00+02:00", + "arrivalTimestamp": "2025-10-15T09:38:00+02:00", + "passlist": [ + { + "name": "Berlin Hbf", + "stationNumber": 8011160, + "departure": "2025-10-14T19:08:00+02:00", + "departureAccessExitAmendable": true, + "departurePlatform": "13", + "gpsCoordinates": [ + 52525589, + 13369549 + ] + }, + { + "name": "Göttingen", + "stationNumber": 8000128, + "departure": "2025-10-14T21:37:00+02:00", + "arrival": "2025-10-14T21:35:00+02:00", + "arrivalAccessExitAmendable": true, + "arrivalPlatform": "10", + "departureAccessExitAmendable": true, + "departurePlatform": "10", + "gpsCoordinates": [ + 51536812, + 9926069 + ] + }, + { + "name": "Kassel-Wilhelmshöhe", + "stationNumber": 8003200, + "departure": "2025-10-14T22:08:00+02:00", + "arrival": "2025-10-14T21:58:00+02:00", + "arrivalAccessExitAmendable": true, + "arrivalPlatform": "1", + "departureAccessExitAmendable": true, + "departurePlatform": "1", + "gpsCoordinates": [ + 51312558, + 9447114 + ] + }, + { + "name": "Frankfurt(Main)Süd", + "stationNumber": 8002041, + "departure": "2025-10-15T00:26:00+02:00", + "arrival": "2025-10-15T00:22:00+02:00", + "arrivalAccessExitAmendable": true, + "arrivalPlatform": "6", + "departureAccessExitAmendable": true, + "departurePlatform": "6", + "gpsCoordinates": [ + 50099365, + 8686456 + ] + }, + { + "name": "Mannheim Hbf", + "stationNumber": 8000244, + "departure": "2025-10-15T03:40:00+02:00", + "arrival": "2025-10-15T01:34:00+02:00", + "arrivalAccessExitAmendable": false, + "departureAccessExitAmendable": false, + "gpsCoordinates": [ + 49479352, + 8468917 + ] + }, + { + "name": "Forbach Frontière de l'État", + "stationNumber": 8002021, + "departure": "2025-10-15T05:11:00+02:00", + "arrival": "2025-10-15T05:11:00+02:00", + "arrivalAccessExitAmendable": false, + "departureAccessExitAmendable": false, + "gpsCoordinates": [ + 49214089, + 6944311 + ] + }, + { + "name": "Paris Est", + "stationNumber": 8700011, + "arrival": "2025-10-15T09:38:00+02:00", + "arrivalAccessExitAmendable": true, + "stopState": [ + "ARR_PROGNOSED" + ], + "gpsCoordinates": [ + 48876976, + 2359120 + ] + } + ], + "attributes": [ + { + "attributeType": "operator", + "range": [ + 0, + 6 + ], + "value": "DPN", + "svalue": "DPN", + "lvalue": "Nahreisezug" + }, + { + "attributeType": "operator_id", + "range": [ + 0, + 6 + ], + "value": "81" + }, + { + "attributeType": "pclass", + "range": [ + 0, + 6 + ], + "value": 3 + }, + { + "attributeType": "intcat", + "range": [ + 0, + 6 + ], + "value": "NJ" + }, + { + "attributeType": "cat", + "range": [ + 0, + 6 + ], + "value": "NJ ", + "svalue": "NJ", + "lvalue": "nightjet", + "code": "3" + }, + { + "attributeType": "name", + "range": [ + 0, + 6 + ], + "value": "NJ 40424" + }, + { + "attributeType": "num", + "range": [ + 0, + 6 + ], + "value": "40424" + }, + { + "attributeType": "intnum", + "range": [ + 0, + 6 + ], + "value": 40424 + }, + { + "attributeType": "generic", + "range": [ + 0, + 6 + ], + "value": "Subject to compulsory reservation", + "code": "RP", + "priority": 1 + }, + { + "attributeType": "generic", + "range": [ + 0, + 6 + ], + "value": "space for wheelchairs", + "code": "RO", + "priority": 150 + }, + { + "attributeType": "generic", + "range": [ + 0, + 6 + ], + "value": "Wheelchair space - For advance notification, call +43 5 1717", + "code": "OA", + "priority": 150 + }, + { + "attributeType": "generic", + "range": [ + 0, + 6 + ], + "value": "WC accessible for wheelchair", + "code": "OC", + "priority": 150 + }, + { + "attributeType": "generic", + "range": [ + 0, + 6 + ], + "value": "Bicycles conveyed - subject to reservation", + "code": "FR", + "priority": 250 + }, + { + "attributeType": "generic", + "range": [ + 0, + 6 + ], + "value": "Number of bicycles conveyed limited", + "code": "FK", + "priority": 250 + }, + { + "attributeType": "generic", + "range": [ + 0, + 6 + ], + "value": "2nd class only seated accommodation", + "code": "J2", + "priority": 300 + }, + { + "attributeType": "generic", + "range": [ + 0, + 6 + ], + "value": "ÖBB Nightjet (www.nightjet.com)", + "code": "OJ", + "priority": 320 + }, + { + "attributeType": "generic", + "range": [ + 0, + 6 + ], + "value": "IRT Integrated Reservation Ticket", + "code": "CT", + "priority": 320 + }, + { + "attributeType": "generic", + "range": [ + 0, + 6 + ], + "value": "Global price", + "code": "GP", + "priority": 320 + }, + { + "attributeType": "generic", + "range": [ + 0, + 6 + ], + "value": "Sleeping-car", + "code": "SW", + "priority": 400 + }, + { + "attributeType": "generic", + "range": [ + 0, + 6 + ], + "value": "Couchettes", + "code": "LW", + "priority": 400 + }, + { + "attributeType": "generic", + "range": [ + 0, + 6 + ], + "value": "snacks and beverages available from sleeper/couchette attendant", + "code": "MN", + "priority": 450 + }, + { + "attributeType": "generic", + "range": [ + 0, + 6 + ], + "value": "Timetable is subject to change or adjustment", + "code": "50", + "priority": 921 + }, + { + "attributeType": "generic", + "range": [ + 0, + 6 + ], + "value": "Hinweis: Fahrzeiten können sich noch ändern.", + "code": "s1", + "priority": 921 + }, + { + "attributeType": "generic", + "range": [ + 0, + 6 + ], + "value": "nightjet", + "code": "ZN", + "priority": 100 + }, + { + "attributeType": "info", + "range": [ + 0, + 6 + ], + "value": "Paris Est", + "code": "RL" + }, + { + "attributeType": "dir", + "range": [ + 0, + 6 + ], + "value": "Paris Est" + } + ], + "i": 0, + "transportType": "journey" + } + ], + "offers": [ + { + "name": "Standard-Ticket inkl. Reservierung", + "productType": "CONNECTION", + "partialOffer": false, + "productDetails": [ + { + "scope": [ + [ + 0, + 0, + 0, + 6 + ] + ], + "title": "ARES Standard-Ticket Nachtverkehr", + "name": { + "de": "Standard-Ticket inkl. Reservierung", + "en": "Standard-Ticket incl. Reservation", + "it": "Standard-Ticket prenotazione incl." + }, + "validityPeriodFrom": "2025-10-14T19:08:00+02:00", + "validityPeriodTo": "2025-10-15T09:38:00+02:00", + "globallyPriced": true, + "objects": [ + { + "index": 0, + "priceClass2": 89.9 + } + ], + "prodGroupLabels": [ + "918 1 Reservierung Default", + "Vollstorno", + "Zugbindung", + "eineFahrt", + "featured" + ], + "validityType": "oneway" + } + ], + "reservation": { + "reservationSegments": [ + { + "scope": [ + 0, + 0, + 0, + 6 + ], + "compartments": [ + { + "externalIdentifier": "couchette4", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "objects": [ + { + "index": 0, + "price": 154.9, + "reservability": "RP" + } + ], + "id": 3756464, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 4 Personen", + "en": "Compartment for 4 passengers", + "fr": ".", + "it": "scompartimento per 4 persone" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 65, + "capacity": 4 + }, + { + "externalIdentifier": "couchette6", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "objects": [ + { + "index": 0, + "price": 134.9, + "reservability": "RP" + } + ], + "id": 3756468, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 6 Personen", + "en": "Compartment for 6 passengers", + "fr": ".", + "it": "Scompartimento per 6 persone" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "in der Mitte", + "en": "middle", + "fr": "in der Mitte", + "it": "preferibilmente centrale" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 45, + "capacity": 6 + }, + { + "externalIdentifier": "singlePlus", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 664.9, + "reservability": "RP" + } + ], + "id": 3756876, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 1 Person mit Dusche/WC (Single plus)", + "en": "Compartment for 1 passenger with shower/WC (Single plus)", + "fr": ".", + "it": "Scompartimento per 1 person1 con doccia/WC (Single plus)" + }, + "spotLocations": [], + "surchargePrice": 575, + "capacity": 1 + }, + { + "externalIdentifier": "privateCouchette", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "privateVariations": [ + { + "count": 1, + "allocations": [ + { + "objects": [ + { + "index": 0, + "price": 564.9 + } + ] + } + ], + "surchargePrice": 475 + } + ], + "id": 5000057, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Privatabteil im Liegewagen für bis zu 6 Personen", + "en": "Private compartment for up to 6 passengers in a couchette coach", + "fr": ".", + "it": "Scompartimento privato in carrozza cuccette per 6 passeggeri al massimo" + }, + "spotLocations": [], + "capacity": 6, + "privateCompartmentContainsSubcompartments": false + }, + { + "externalIdentifier": "privateSeat", + "freeFollowupReservationApplied": false, + "accommodationType": "SE", + "special": false, + "privateVariations": [ + { + "count": 1, + "allocations": [ + { + "objects": [ + { + "index": 0, + "price": 424.9 + } + ] + } + ], + "surchargePrice": 335 + } + ], + "id": 5000059, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Privatabteil im Sitzwagen 2. Klasse", + "en": "Private compartment in a 2nd class seated coach", + "fr": ".", + "it": "Scompartimento privato in carrozza con posti a sedere, 2a classe" + }, + "spotLocations": [], + "capacity": 6, + "privateCompartmentContainsSubcompartments": false + }, + { + "externalIdentifier": "femaleCouchette4", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "objects": [ + { + "index": 0, + "price": 154.9, + "reservability": "RP" + } + ], + "id": 5000068, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil für 4 Personen", + "en": "Ladies only compartment for 4 passengers", + "fr": ".", + "it": "Scompartimento donna per 4 persone" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente finestrinopreferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 65, + "capacity": 4 + }, + { + "externalIdentifier": "femaleCouchette6", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "objects": [ + { + "index": 0, + "price": 134.9, + "reservability": "RP" + } + ], + "id": 5000069, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil für 6 Personen", + "en": "Ladies only compartment for 6 passengers", + "fr": ".", + "it": "Scompartimento donna per 6 persone" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente finestrinopreferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "in der Mitte", + "en": "middle", + "fr": "in der Mitte", + "it": "preferibilmente centrale" + } + } + ], + "surchargePrice": 45, + "capacity": 6 + }, + { + "externalIdentifier": "sideCorridorCoach_2", + "freeFollowupReservationApplied": false, + "accommodationType": "SE", + "special": false, + "objects": [ + { + "index": 0, + "price": 89.9, + "reservability": "RP" + } + ], + "id": 5000075, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Sitzplatz 2. Klasse", + "en": "Seat 2nd class", + "fr": ".", + "it": "Posto a sedere in 2a classe" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "möglichst am Fenster", + "en": "if possible at the window", + "fr": "möglichst am Fenster", + "it": "preferibilmente finestrino" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "möglichst in der Mitte", + "en": "if possible in the center", + "fr": "möglichst in der Mitte", + "it": "preferibilmente centrale" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "möglichst am Gang", + "en": "if possible at the corridor", + "fr": "möglichst am Gang", + "it": "preferibilmente corridoio" + } + }, + { + "param": "NECESSARILY_WINDOW_LOWER", + "name": { + "de": "unbedingt am Fenster", + "en": "absolutely situated at the window", + "fr": "unbedingt am Fenster", + "it": "assolutamente finestrino" + } + } + ], + "capacity": 6 + }, + { + "externalIdentifier": "privateCouchette4comfort", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "privateVariations": [ + { + "count": 1, + "allocations": [ + { + "objects": [ + { + "index": 0, + "price": 564.9 + } + ] + } + ], + "surchargePrice": 475 + } + ], + "id": 5000085, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Privatabteil comfort im Liegewagen für bis zu 4 Personen", + "en": "Comfort private compartment in a couchette coach for up to 4 passengers", + "fr": ".", + "it": "Scompartimento privato comfort per 4 persone al massimo" + }, + "spotLocations": [], + "capacity": 4, + "privateCompartmentContainsSubcompartments": false + }, + { + "externalIdentifier": "couchette4comfort", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "objects": [ + { + "index": 0, + "price": 154.9, + "reservability": "RP" + } + ], + "id": 5000107, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil comfort für 4 Personen", + "en": "Comfort compartment for 4 passengers", + "fr": ".", + "it": "Scompartimento comfort per 4 persone" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 65, + "capacity": 4 + }, + { + "externalIdentifier": "femaleCouchette4comfort", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "objects": [ + { + "index": 0, + "price": 154.9, + "reservability": "RP" + } + ], + "id": 5000108, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil comfort für 4 Personen", + "en": "Comfort ladies only compartment for 4 passengers", + "fr": ".", + "it": "Scompartimento donna comfort per 4 persone" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 65, + "capacity": 4 + }, + { + "externalIdentifier": "femaleT3Plus", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 264.9, + "reservability": "RP" + } + ], + "id": 5000151, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil für 3 Personen mit Dusche/WC (Triple plus)", + "en": "Ladies only compartment for 3 passengers with shower/WC (Triple plus)", + "fr": ".", + "it": "Scompartimento donna per 3 persone con doccia/WC (Triple plus)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "in der Mitte", + "en": "middle", + "fr": "in der Mitte", + "it": "preferibilmente centrale" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 175, + "capacity": 3 + }, + { + "externalIdentifier": "mixedT3Plus", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 264.9, + "reservability": "RP" + } + ], + "id": 5000153, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 3 Personen mit Dusche/WC (Triple plus)", + "en": "Compartment for 3 passengers with shower/WC (Triple plus)", + "fr": ".", + "it": "Scompartimento per 3 persone con doccia/WC (Triple plus)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "in der Mitte", + "en": "middle", + "fr": "in der Mitte", + "it": "preferibilmente centrale" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 175, + "capacity": 3 + }, + { + "externalIdentifier": "mixedDoublePlus", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 329.9, + "reservability": "RP" + } + ], + "id": 5000160, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 2 Personen mit Dusche/WC (Double plus)", + "en": "Compartment for 2 passengers with shower/WC (Double plus)", + "fr": ".", + "it": "Scompartimento per 2 persone con doccia/WC (Double plus)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 240, + "capacity": 2 + }, + { + "externalIdentifier": "femaleDoublePlus", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 329.9, + "reservability": "RP" + } + ], + "id": 5000162, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil für 2 Personen mit Dusche/WC (Double plus)", + "en": "Ladies only compartment for 2 passengers with shower/WC (Double plus)", + "fr": ".", + "it": "Scompartimento donna per 2 persone con doccia/WC (Double plus)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 240, + "capacity": 2 + }, + { + "externalIdentifier": "single", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 514.9, + "reservability": "RP" + } + ], + "id": 5000198, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 1 Person (Single)", + "en": "Compartment for 1 passenger (Single)", + "fr": ".", + "it": "Scompartimento per 1 persona (Single)" + }, + "spotLocations": [], + "surchargePrice": 425, + "capacity": 1 + }, + { + "externalIdentifier": "femaleDouble", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 259.9, + "reservability": "RP" + } + ], + "id": 5000200, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil für 2 Personen (Double)", + "en": "Ladies only compartment for 2 passengers (Double)", + "fr": ".", + "it": "Scompartimento donna per 2 persone (Double)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 170, + "capacity": 2 + }, + { + "externalIdentifier": "mixedDouble", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 259.9, + "reservability": "RP" + } + ], + "id": 5000201, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 2 Personen (Double)", + "en": "Compartment for 2 passengers (Double)", + "fr": ".", + "it": "Scompartimento per 2 persone (Double)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 170, + "capacity": 2 + }, + { + "externalIdentifier": "femaleT3", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 204.9, + "reservability": "RP" + } + ], + "id": 5000202, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil für 3 Personen (Triple)", + "en": "Ladies only compartment for 3 passengers (Triple)", + "fr": ".", + "it": "Scompartimento donna per 3 persone (Triple)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "in der Mitte", + "en": "middle", + "fr": "in der Mitte", + "it": "preferibilmente centrale" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 115, + "capacity": 3 + }, + { + "externalIdentifier": "mixedT3", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 204.9, + "reservability": "RP" + } + ], + "id": 5000204, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 3 Personen (Triple)", + "en": "Compartment for 3 passengers (Triple)", + "fr": ".", + "it": "Scompartimento per 3 persone (Triple)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "in der Mitte", + "en": "middle", + "fr": "in der Mitte", + "it": "preferibilmente centrale" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 115, + "capacity": 3 + } + ], + "reservability": "RP" + } + ], + "priceClass2": 0 + }, + "priceClass2": 89.9, + "refCards": [], + "co2Savings": 252.89, + "outputTypes": [ + "MOBILE", + "PDF", + "SECURITY_PAPER" + ], + "prodGroupLabels": [ + "918 1 Reservierung Default", + "Vollstorno", + "Zugbindung", + "eineFahrt", + "featured" + ], + "validityPeriodFrom": "2025-10-14T19:08:00.000+02:00", + "validityPeriodTo": "2025-10-15T09:38:00.000+02:00", + "validityType": "oneway", + "rideBound": true, + "singleVariety": true, + "reservationMandatory": true + }, + { + "name": "Sparschiene Komfort inkl. Reservierung", + "productType": "CONNECTION", + "partialOffer": false, + "productDetails": [ + { + "scope": [ + [ + 0, + 0, + 0, + 6 + ] + ], + "title": "ARES Sparschiene Komfort Nachtverkehr", + "name": { + "de": "Sparschiene Komfort inkl. Reservierung", + "en": "Sparschiene Komfort incl. Reservation", + "it": "Sparschiene Komfort prenotazione incl." + }, + "validityPeriodFrom": "2025-10-14T19:08:00+02:00", + "validityPeriodTo": "2025-10-15T09:38:00+02:00", + "globallyPriced": true, + "objects": [ + { + "index": 0, + "priceClass2": 64.9 + } + ], + "prodGroupLabels": [ + "Zugbindung", + "eineFahrt", + "featured", + "komfortticketStorno" + ], + "validityType": "oneway" + } + ], + "reservation": { + "reservationSegments": [ + { + "scope": [ + 0, + 0, + 0, + 6 + ], + "compartments": [ + { + "externalIdentifier": "couchette4", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "objects": [ + { + "index": 0, + "price": 129.9, + "reservability": "RP" + } + ], + "id": 3756464, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 4 Personen", + "en": "Compartment for 4 passengers", + "fr": ".", + "it": "scompartimento per 4 persone" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 65, + "capacity": 4 + }, + { + "externalIdentifier": "couchette6", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "objects": [ + { + "index": 0, + "price": 109.9, + "reservability": "RP" + } + ], + "id": 3756468, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 6 Personen", + "en": "Compartment for 6 passengers", + "fr": ".", + "it": "Scompartimento per 6 persone" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "in der Mitte", + "en": "middle", + "fr": "in der Mitte", + "it": "preferibilmente centrale" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 45, + "capacity": 6 + }, + { + "externalIdentifier": "singlePlus", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 494.9, + "reservability": "RP" + } + ], + "id": 3756876, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 1 Person mit Dusche/WC (Single plus)", + "en": "Compartment for 1 passenger with shower/WC (Single plus)", + "fr": ".", + "it": "Scompartimento per 1 person1 con doccia/WC (Single plus)" + }, + "spotLocations": [], + "surchargePrice": 430, + "capacity": 1 + }, + { + "externalIdentifier": "privateCouchette", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "privateVariations": [ + { + "count": 1, + "allocations": [ + { + "objects": [ + { + "index": 0, + "price": 519.9 + } + ] + } + ], + "surchargePrice": 455 + } + ], + "id": 5000057, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Privatabteil im Liegewagen für bis zu 6 Personen", + "en": "Private compartment for up to 6 passengers in a couchette coach", + "fr": ".", + "it": "Scompartimento privato in carrozza cuccette per 6 passeggeri al massimo" + }, + "spotLocations": [], + "capacity": 6, + "privateCompartmentContainsSubcompartments": false + }, + { + "externalIdentifier": "privateSeat", + "freeFollowupReservationApplied": false, + "accommodationType": "SE", + "special": false, + "privateVariations": [ + { + "count": 1, + "allocations": [ + { + "objects": [ + { + "index": 0, + "price": 319.9 + } + ] + } + ], + "surchargePrice": 255 + } + ], + "id": 5000059, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Privatabteil im Sitzwagen 2. Klasse", + "en": "Private compartment in a 2nd class seated coach", + "fr": ".", + "it": "Scompartimento privato in carrozza con posti a sedere, 2a classe" + }, + "spotLocations": [], + "capacity": 6, + "privateCompartmentContainsSubcompartments": false + }, + { + "externalIdentifier": "femaleCouchette4", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "objects": [ + { + "index": 0, + "price": 129.9, + "reservability": "RP" + } + ], + "id": 5000068, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil für 4 Personen", + "en": "Ladies only compartment for 4 passengers", + "fr": ".", + "it": "Scompartimento donna per 4 persone" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente finestrinopreferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 65, + "capacity": 4 + }, + { + "externalIdentifier": "femaleCouchette6", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "objects": [ + { + "index": 0, + "price": 109.9, + "reservability": "RP" + } + ], + "id": 5000069, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil für 6 Personen", + "en": "Ladies only compartment for 6 passengers", + "fr": ".", + "it": "Scompartimento donna per 6 persone" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente finestrinopreferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "in der Mitte", + "en": "middle", + "fr": "in der Mitte", + "it": "preferibilmente centrale" + } + } + ], + "surchargePrice": 45, + "capacity": 6 + }, + { + "externalIdentifier": "sideCorridorCoach_2", + "freeFollowupReservationApplied": false, + "accommodationType": "SE", + "special": false, + "objects": [ + { + "index": 0, + "price": 64.9, + "reservability": "RP" + } + ], + "id": 5000075, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Sitzplatz 2. Klasse", + "en": "Seat 2nd class", + "fr": ".", + "it": "Posto a sedere in 2a classe" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "möglichst am Fenster", + "en": "if possible at the window", + "fr": "möglichst am Fenster", + "it": "preferibilmente finestrino" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "möglichst in der Mitte", + "en": "if possible in the center", + "fr": "möglichst in der Mitte", + "it": "preferibilmente centrale" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "möglichst am Gang", + "en": "if possible at the corridor", + "fr": "möglichst am Gang", + "it": "preferibilmente corridoio" + } + }, + { + "param": "NECESSARILY_WINDOW_LOWER", + "name": { + "de": "unbedingt am Fenster", + "en": "absolutely situated at the window", + "fr": "unbedingt am Fenster", + "it": "assolutamente finestrino" + } + } + ], + "capacity": 6 + }, + { + "externalIdentifier": "privateCouchette4comfort", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "privateVariations": [ + { + "count": 1, + "allocations": [ + { + "objects": [ + { + "index": 0, + "price": 519.9 + } + ] + } + ], + "surchargePrice": 455 + } + ], + "id": 5000085, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Privatabteil comfort im Liegewagen für bis zu 4 Personen", + "en": "Comfort private compartment in a couchette coach for up to 4 passengers", + "fr": ".", + "it": "Scompartimento privato comfort per 4 persone al massimo" + }, + "spotLocations": [], + "capacity": 4, + "privateCompartmentContainsSubcompartments": false + }, + { + "externalIdentifier": "couchette4comfort", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "objects": [ + { + "index": 0, + "price": 129.9, + "reservability": "RP" + } + ], + "id": 5000107, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil comfort für 4 Personen", + "en": "Comfort compartment for 4 passengers", + "fr": ".", + "it": "Scompartimento comfort per 4 persone" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 65, + "capacity": 4 + }, + { + "externalIdentifier": "femaleCouchette4comfort", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "objects": [ + { + "index": 0, + "price": 129.9, + "reservability": "RP" + } + ], + "id": 5000108, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil comfort für 4 Personen", + "en": "Comfort ladies only compartment for 4 passengers", + "fr": ".", + "it": "Scompartimento donna comfort per 4 persone" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 65, + "capacity": 4 + }, + { + "externalIdentifier": "femaleT3Plus", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 199.9, + "reservability": "RP" + } + ], + "id": 5000151, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil für 3 Personen mit Dusche/WC (Triple plus)", + "en": "Ladies only compartment for 3 passengers with shower/WC (Triple plus)", + "fr": ".", + "it": "Scompartimento donna per 3 persone con doccia/WC (Triple plus)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "in der Mitte", + "en": "middle", + "fr": "in der Mitte", + "it": "preferibilmente centrale" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 135, + "capacity": 3 + }, + { + "externalIdentifier": "mixedT3Plus", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 199.9, + "reservability": "RP" + } + ], + "id": 5000153, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 3 Personen mit Dusche/WC (Triple plus)", + "en": "Compartment for 3 passengers with shower/WC (Triple plus)", + "fr": ".", + "it": "Scompartimento per 3 persone con doccia/WC (Triple plus)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "in der Mitte", + "en": "middle", + "fr": "in der Mitte", + "it": "preferibilmente centrale" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 135, + "capacity": 3 + }, + { + "externalIdentifier": "mixedDoublePlus", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 249.9, + "reservability": "RP" + } + ], + "id": 5000160, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 2 Personen mit Dusche/WC (Double plus)", + "en": "Compartment for 2 passengers with shower/WC (Double plus)", + "fr": ".", + "it": "Scompartimento per 2 persone con doccia/WC (Double plus)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 185, + "capacity": 2 + }, + { + "externalIdentifier": "femaleDoublePlus", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 249.9, + "reservability": "RP" + } + ], + "id": 5000162, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil für 2 Personen mit Dusche/WC (Double plus)", + "en": "Ladies only compartment for 2 passengers with shower/WC (Double plus)", + "fr": ".", + "it": "Scompartimento donna per 2 persone con doccia/WC (Double plus)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 185, + "capacity": 2 + }, + { + "externalIdentifier": "single", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 384.9, + "reservability": "RP" + } + ], + "id": 5000198, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 1 Person (Single)", + "en": "Compartment for 1 passenger (Single)", + "fr": ".", + "it": "Scompartimento per 1 persona (Single)" + }, + "spotLocations": [], + "surchargePrice": 320, + "capacity": 1 + }, + { + "externalIdentifier": "femaleDouble", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 194.9, + "reservability": "RP" + } + ], + "id": 5000200, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil für 2 Personen (Double)", + "en": "Ladies only compartment for 2 passengers (Double)", + "fr": ".", + "it": "Scompartimento donna per 2 persone (Double)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 130, + "capacity": 2 + }, + { + "externalIdentifier": "mixedDouble", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 194.9, + "reservability": "RP" + } + ], + "id": 5000201, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 2 Personen (Double)", + "en": "Compartment for 2 passengers (Double)", + "fr": ".", + "it": "Scompartimento per 2 persone (Double)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 130, + "capacity": 2 + }, + { + "externalIdentifier": "femaleT3", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 169.9, + "reservability": "RP" + } + ], + "id": 5000202, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil für 3 Personen (Triple)", + "en": "Ladies only compartment for 3 passengers (Triple)", + "fr": ".", + "it": "Scompartimento donna per 3 persone (Triple)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "in der Mitte", + "en": "middle", + "fr": "in der Mitte", + "it": "preferibilmente centrale" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 105, + "capacity": 3 + }, + { + "externalIdentifier": "mixedT3", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 169.9, + "reservability": "RP" + } + ], + "id": 5000204, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 3 Personen (Triple)", + "en": "Compartment for 3 passengers (Triple)", + "fr": ".", + "it": "Scompartimento per 3 persone (Triple)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "in der Mitte", + "en": "middle", + "fr": "in der Mitte", + "it": "preferibilmente centrale" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 105, + "capacity": 3 + } + ], + "reservability": "RP" + } + ], + "priceClass2": 0 + }, + "priceClass2": 64.9, + "refCards": [], + "co2Savings": 252.89, + "outputTypes": [ + "MOBILE", + "PDF", + "SECURITY_PAPER" + ], + "prodGroupLabels": [ + "Zugbindung", + "eineFahrt", + "featured", + "komfortticketStorno" + ], + "validityPeriodFrom": "2025-10-14T19:08:00.000+02:00", + "validityPeriodTo": "2025-10-15T09:38:00.000+02:00", + "validityType": "oneway", + "rideBound": true, + "singleVariety": true, + "reservationMandatory": true + }, + { + "name": "Sparschiene inkl. Reservierung", + "productType": "CONNECTION", + "partialOffer": false, + "productDetails": [ + { + "scope": [ + [ + 0, + 0, + 0, + 6 + ] + ], + "title": "ARES Sparschiene Nachtverkehr", + "name": { + "de": "Sparschiene inkl. Reservierung", + "en": "Sparschiene incl. Reservation", + "it": "Sparschiene prenotazione incl." + }, + "validityPeriodFrom": "2025-10-14T19:08:00+02:00", + "validityPeriodTo": "2025-10-15T09:38:00+02:00", + "globallyPriced": true, + "objects": [ + { + "index": 0, + "priceClass2": 44.9 + } + ], + "prodGroupLabels": [ + "Kein Storno", + "Zugbindung", + "eineFahrt", + "featured", + "nightjetSparschiene" + ], + "validityType": "oneway" + } + ], + "reservation": { + "reservationSegments": [ + { + "scope": [ + 0, + 0, + 0, + 6 + ], + "compartments": [ + { + "externalIdentifier": "couchette4", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "objects": [ + { + "index": 0, + "price": 114.9, + "reservability": "RP" + } + ], + "id": 3756464, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 4 Personen", + "en": "Compartment for 4 passengers", + "fr": ".", + "it": "scompartimento per 4 persone" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 70, + "capacity": 4 + }, + { + "externalIdentifier": "couchette6", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "objects": [ + { + "index": 0, + "price": 99.9, + "reservability": "RP" + } + ], + "id": 3756468, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 6 Personen", + "en": "Compartment for 6 passengers", + "fr": ".", + "it": "Scompartimento per 6 persone" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "in der Mitte", + "en": "middle", + "fr": "in der Mitte", + "it": "preferibilmente centrale" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 55, + "capacity": 6 + }, + { + "externalIdentifier": "singlePlus", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 349.9, + "reservability": "RP" + } + ], + "id": 3756876, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 1 Person mit Dusche/WC (Single plus)", + "en": "Compartment for 1 passenger with shower/WC (Single plus)", + "fr": ".", + "it": "Scompartimento per 1 person1 con doccia/WC (Single plus)" + }, + "spotLocations": [], + "surchargePrice": 305, + "capacity": 1 + }, + { + "externalIdentifier": "privateCouchette", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "privateVariations": [ + { + "count": 1, + "allocations": [ + { + "objects": [ + { + "index": 0, + "price": 494.9 + } + ] + } + ], + "surchargePrice": 450 + } + ], + "id": 5000057, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Privatabteil im Liegewagen für bis zu 6 Personen", + "en": "Private compartment for up to 6 passengers in a couchette coach", + "fr": ".", + "it": "Scompartimento privato in carrozza cuccette per 6 passeggeri al massimo" + }, + "spotLocations": [], + "capacity": 6, + "privateCompartmentContainsSubcompartments": false + }, + { + "externalIdentifier": "privateSeat", + "freeFollowupReservationApplied": false, + "accommodationType": "SE", + "special": false, + "privateVariations": [ + { + "count": 1, + "allocations": [ + { + "objects": [ + { + "index": 0, + "price": 224.9 + } + ] + } + ], + "surchargePrice": 180 + } + ], + "id": 5000059, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Privatabteil im Sitzwagen 2. Klasse", + "en": "Private compartment in a 2nd class seated coach", + "fr": ".", + "it": "Scompartimento privato in carrozza con posti a sedere, 2a classe" + }, + "spotLocations": [], + "capacity": 6, + "privateCompartmentContainsSubcompartments": false + }, + { + "externalIdentifier": "femaleCouchette4", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "objects": [ + { + "index": 0, + "price": 114.9, + "reservability": "RP" + } + ], + "id": 5000068, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil für 4 Personen", + "en": "Ladies only compartment for 4 passengers", + "fr": ".", + "it": "Scompartimento donna per 4 persone" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente finestrinopreferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 70, + "capacity": 4 + }, + { + "externalIdentifier": "femaleCouchette6", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "objects": [ + { + "index": 0, + "price": 99.9, + "reservability": "RP" + } + ], + "id": 5000069, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil für 6 Personen", + "en": "Ladies only compartment for 6 passengers", + "fr": ".", + "it": "Scompartimento donna per 6 persone" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente finestrinopreferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "in der Mitte", + "en": "middle", + "fr": "in der Mitte", + "it": "preferibilmente centrale" + } + } + ], + "surchargePrice": 55, + "capacity": 6 + }, + { + "externalIdentifier": "sideCorridorCoach_2", + "freeFollowupReservationApplied": false, + "accommodationType": "SE", + "special": false, + "objects": [ + { + "index": 0, + "price": 44.9, + "reservability": "RP" + } + ], + "id": 5000075, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Sitzplatz 2. Klasse", + "en": "Seat 2nd class", + "fr": ".", + "it": "Posto a sedere in 2a classe" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "möglichst am Fenster", + "en": "if possible at the window", + "fr": "möglichst am Fenster", + "it": "preferibilmente finestrino" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "möglichst in der Mitte", + "en": "if possible in the center", + "fr": "möglichst in der Mitte", + "it": "preferibilmente centrale" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "möglichst am Gang", + "en": "if possible at the corridor", + "fr": "möglichst am Gang", + "it": "preferibilmente corridoio" + } + }, + { + "param": "NECESSARILY_WINDOW_LOWER", + "name": { + "de": "unbedingt am Fenster", + "en": "absolutely situated at the window", + "fr": "unbedingt am Fenster", + "it": "assolutamente finestrino" + } + } + ], + "capacity": 6 + }, + { + "externalIdentifier": "privateCouchette4comfort", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "privateVariations": [ + { + "count": 1, + "allocations": [ + { + "objects": [ + { + "index": 0, + "price": 494.9 + } + ] + } + ], + "surchargePrice": 450 + } + ], + "id": 5000085, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Privatabteil comfort im Liegewagen für bis zu 4 Personen", + "en": "Comfort private compartment in a couchette coach for up to 4 passengers", + "fr": ".", + "it": "Scompartimento privato comfort per 4 persone al massimo" + }, + "spotLocations": [], + "capacity": 4, + "privateCompartmentContainsSubcompartments": false + }, + { + "externalIdentifier": "couchette4comfort", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "objects": [ + { + "index": 0, + "price": 114.9, + "reservability": "RP" + } + ], + "id": 5000107, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil comfort für 4 Personen", + "en": "Comfort compartment for 4 passengers", + "fr": ".", + "it": "Scompartimento comfort per 4 persone" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 70, + "capacity": 4 + }, + { + "externalIdentifier": "femaleCouchette4comfort", + "freeFollowupReservationApplied": false, + "accommodationType": "LE", + "special": false, + "objects": [ + { + "index": 0, + "price": 114.9, + "reservability": "RP" + } + ], + "id": 5000108, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil comfort für 4 Personen", + "en": "Comfort ladies only compartment for 4 passengers", + "fr": ".", + "it": "Scompartimento donna comfort per 4 persone" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 70, + "capacity": 4 + }, + { + "externalIdentifier": "femaleT3Plus", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 139.9, + "reservability": "RP" + } + ], + "id": 5000151, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil für 3 Personen mit Dusche/WC (Triple plus)", + "en": "Ladies only compartment for 3 passengers with shower/WC (Triple plus)", + "fr": ".", + "it": "Scompartimento donna per 3 persone con doccia/WC (Triple plus)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "in der Mitte", + "en": "middle", + "fr": "in der Mitte", + "it": "preferibilmente centrale" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 95, + "capacity": 3 + }, + { + "externalIdentifier": "mixedT3Plus", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 139.9, + "reservability": "RP" + } + ], + "id": 5000153, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 3 Personen mit Dusche/WC (Triple plus)", + "en": "Compartment for 3 passengers with shower/WC (Triple plus)", + "fr": ".", + "it": "Scompartimento per 3 persone con doccia/WC (Triple plus)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "in der Mitte", + "en": "middle", + "fr": "in der Mitte", + "it": "preferibilmente centrale" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 95, + "capacity": 3 + }, + { + "externalIdentifier": "mixedDoublePlus", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 174.9, + "reservability": "RP" + } + ], + "id": 5000160, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 2 Personen mit Dusche/WC (Double plus)", + "en": "Compartment for 2 passengers with shower/WC (Double plus)", + "fr": ".", + "it": "Scompartimento per 2 persone con doccia/WC (Double plus)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 130, + "capacity": 2 + }, + { + "externalIdentifier": "femaleDoublePlus", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 174.9, + "reservability": "RP" + } + ], + "id": 5000162, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil für 2 Personen mit Dusche/WC (Double plus)", + "en": "Ladies only compartment for 2 passengers with shower/WC (Double plus)", + "fr": ".", + "it": "Scompartimento donna per 2 persone con doccia/WC (Double plus)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 130, + "capacity": 2 + }, + { + "externalIdentifier": "single", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 334.9, + "reservability": "RP" + } + ], + "id": 5000198, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 1 Person (Single)", + "en": "Compartment for 1 passenger (Single)", + "fr": ".", + "it": "Scompartimento per 1 persona (Single)" + }, + "spotLocations": [], + "surchargePrice": 290, + "capacity": 1 + }, + { + "externalIdentifier": "femaleDouble", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 169.9, + "reservability": "RP" + } + ], + "id": 5000200, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil für 2 Personen (Double)", + "en": "Ladies only compartment for 2 passengers (Double)", + "fr": ".", + "it": "Scompartimento donna per 2 persone (Double)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 125, + "capacity": 2 + }, + { + "externalIdentifier": "mixedDouble", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 169.9, + "reservability": "RP" + } + ], + "id": 5000201, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 2 Personen (Double)", + "en": "Compartment for 2 passengers (Double)", + "fr": ".", + "it": "Scompartimento per 2 persone (Double)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 125, + "capacity": 2 + }, + { + "externalIdentifier": "femaleT3", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 154.9, + "reservability": "RP" + } + ], + "id": 5000202, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Damenabteil für 3 Personen (Triple)", + "en": "Ladies only compartment for 3 passengers (Triple)", + "fr": ".", + "it": "Scompartimento donna per 3 persone (Triple)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "in der Mitte", + "en": "middle", + "fr": "in der Mitte", + "it": "preferibilmente centrale" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 110, + "capacity": 3 + }, + { + "externalIdentifier": "mixedT3", + "freeFollowupReservationApplied": false, + "accommodationType": "BE", + "special": false, + "objects": [ + { + "index": 0, + "price": 154.9, + "reservability": "RP" + } + ], + "id": 5000204, + "accommodationClass": [ + "class2" + ], + "name": { + "de": "Abteil für 3 Personen (Triple)", + "en": "Compartment for 3 passengers (Triple)", + "fr": ".", + "it": "Scompartimento per 3 persone (Triple)" + }, + "spotLocations": [ + { + "param": "WINDOW_UPPER", + "name": { + "de": "oben", + "en": "top", + "fr": "oben", + "it": "preferibilmente in alto" + } + }, + { + "param": "MIDDLE", + "name": { + "de": "in der Mitte", + "en": "middle", + "fr": "in der Mitte", + "it": "preferibilmente centrale" + } + }, + { + "param": "AISLE_LOWER", + "name": { + "de": "unten", + "en": "lower", + "fr": "unten", + "it": "preferibilmente in basso" + } + } + ], + "surchargePrice": 110, + "capacity": 3 + } + ], + "reservability": "RP" + } + ], + "priceClass2": 0 + }, + "priceClass2": 44.9, + "refCards": [], + "co2Savings": 252.89, + "outputTypes": [ + "MOBILE", + "PDF", + "SECURITY_PAPER" + ], + "prodGroupLabels": [ + "Kein Storno", + "Zugbindung", + "eineFahrt", + "featured", + "nightjetSparschiene" + ], + "validityPeriodFrom": "2025-10-14T19:08:00.000+02:00", + "validityPeriodTo": "2025-10-15T09:38:00.000+02:00", + "validityType": "oneway", + "rideBound": true, + "singleVariety": true, + "reservationMandatory": true + } + ], + "informationMessages": [ + { + "category": 99, + "validFrom": "2025-10-15T09:38:00+02:00", + "validTo": "2025-10-14T19:08:00+02:00", + "sectionIndex": 0, + "header": "Please note – timetable subject to change", + "text": "Due to construction work or other factors affecting our services, the departure and/or arrival times of this service may change even on the selected day. If you buy a ticket, we will send you an email or sms text message to notify you about any modifications. (Please also check your spam folder regularly).
If the departure time changes 60 minutes or more, a refund is also possible and free of charge for a Sparschiene saver ticket up to 15 days before the first day of validity.
", + "textPlain": "Due to construction work or other factors affecting our services, the departure and/or arrival times of this service may change even on the selected day. If you buy a ticket, we will send you an email or sms text message to notify you about any modifications. (Please also check your spam folder regularly).If the departure time changes 60 minutes or more, a refund is also possible and free of charge for a Sparschiene saver ticket up to 15 days before the first day of validity." + } + ], + "njIndex": 0 + } + ], + "filter": { + "maxTrainChanges": 0 + }, + "rfpIndex": 0 + } + ], + "ok": true, + "responseCode": 200 +} +``` + diff --git a/pyproject.toml b/pyproject.toml index 0fc68a9..488ab35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,21 @@ [project] -name = "nj-api" +name = "nightjetter" version = "0.1.0" -description = "Add your description here" +description = "Continuously monitors nightjet travel dates for price fluctuations" readme = "README.md" +authors = [{ name = "Marty Oehme", email = "contact@martyoeh.me" }] requires-python = ">=3.13" -dependencies = [ - "requests>=2.32.4", - "typer>=0.16.0", -] +dependencies = ["requests>=2.32.4", "typer>=0.16.0"] + +[project.scripts] +nightjet = "nj:main" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" [tool.pyright] typeCheckingMode = "basic" + +[tool.hatch.build.targets.wheel] +packages = ["src/nj"] diff --git a/src/nj/__init__.py b/src/nj/__init__.py new file mode 100644 index 0000000..ab66342 --- /dev/null +++ b/src/nj/__init__.py @@ -0,0 +1,5 @@ +from nj import main as cli + + +def main() -> None: + cli.app() diff --git a/main.py b/src/nj/main.py similarity index 62% rename from main.py rename to src/nj/main.py index c5f406c..b1c62c4 100644 --- a/main.py +++ b/src/nj/main.py @@ -3,20 +3,23 @@ import json from dataclasses import dataclass from datetime import datetime from pathlib import Path -from typing import Any +from time import sleep +from typing import Annotated, Any import requests import typer BASE_URL = "https://www.nightjet.com" BASE_DIR = "out" -CSV_LOWEST_FILE = f"{BASE_DIR}/lowest.csv" -CSV_ALL_PRICES_PATTERN = f"{BASE_DIR}/%%DATE%%_all_prices.csv" +CSV_LOWEST_FILE = "lowest.csv" +CSV_ALL_PRICES_PATTERN = "all_prices_%%DATE%%.csv" NOTIFICATION_CHANNEL = "nightjet-price-notifier" START_STATION = "8096003" # BerlinHBF END_STATION = "8796001" # Paris Est -TRAVEL_DATE = "2025-10-14" +TRAVELLER_BIRTHDATE = datetime(1990, 1, 1) # TODO: could randomize a little + +MONITOR_FREQUENCY = 3600 def dprint(txt) -> None: @@ -61,10 +64,9 @@ def request_connections( return resp_json["connections"] -TRAVELLER_BIRTHDATE = "2000-07-15" # TODO: randomize a little - - -def connection_data_to_booking_requests(connections) -> list[dict[str, Any]]: +def connection_data_to_booking_requests( + connections, traveller_birthdate: datetime = TRAVELLER_BIRTHDATE +) -> list[dict[str, Any]]: b_requests = [] for c in connections: train = c["trains"][0] @@ -80,7 +82,11 @@ def connection_data_to_booking_requests(connections) -> list[dict[str, Any]]: "njDeparture": dep, # departure time again }, "objects": [ # traveller - {"type": "person", "birthDate": TRAVELLER_BIRTHDATE, "cards": []} + { + "type": "person", + "birthDate": traveller_birthdate.strftime("%Y-%m-%d"), + "cards": [], + } ], "relations": [], "lang": "en", @@ -193,22 +199,35 @@ def get_lowest_price(prices: list[Price]) -> Price: return lowest -def dump_all_prices_to_csv(prices: list[Price]) -> None: - fname = CSV_ALL_PRICES_PATTERN.replace( - "%%DATE%%", str(int(datetime.now().timestamp())) +def dump_all_prices_to_csv(prices: list[Price], fpath: Path) -> None: + fstr = str(fpath) + fpath_replaced = Path( + fstr.replace("%%DATE%%", str(int(datetime.now().timestamp()))) ) - with open(fname, "w") as f: + with open(fpath_replaced, "w") as f: writer = csv.writer(f) - writer.writerow(["id", "price", "name"]) - writer.writerows([[price.id, price.price, price.name] for price in prices]) + writer.writerow(["id", "price", "ts_from", "ts_to", "name"]) + writer.writerows( + [ + [ + price.id, + price.price, + price.dt_from.timestamp(), + price.dt_to.timestamp(), + price.name, + ] + for price in prices + ] + ) + dprint(f"Dumped current query snapshot into: {fpath_replaced}.") -def add_to_csv(price: Price) -> None: - if not Path(CSV_LOWEST_FILE).is_file(): - with open(CSV_LOWEST_FILE, "w") as f: +def add_to_csv(price: Price, file: Path) -> None: + if not file.is_file(): + with open(file, "w") as f: csv.writer(f).writerow(["id", "price", "ts_from", "ts_to", "name"]) - with open(CSV_LOWEST_FILE, "a") as f: + with open(file, "a") as f: csv.writer(f).writerow( [ price.id, @@ -220,11 +239,11 @@ def add_to_csv(price: Price) -> None: ) -def get_last_price_from_csv() -> Price | None: - if not Path(CSV_LOWEST_FILE).is_file(): +def get_last_price_from_csv(file: Path) -> Price | None: + if not file.is_file(): return - with open(CSV_LOWEST_FILE) as f: + with open(file) as f: last = next(reversed(list(csv.reader(f)))) return Price( id=last[0], @@ -247,40 +266,14 @@ def notify_user(previous: Price, new: Price, channel: str) -> None: ) -def main(start_station: int, end_station: int, travel_date: datetime): - Path(BASE_DIR).mkdir(exist_ok=True, parents=True) - print(start_station, end_station, travel_date) - # return - +def query(start_station: int, end_station: int, travel_date: datetime, traveller_birthdate: datetime) -> list[Price]: token = request_init_token() connections = request_connections(token, start_station, end_station, travel_date) - booking_requests = connection_data_to_booking_requests(connections) + booking_requests = connection_data_to_booking_requests(connections, traveller_birthdate=traveller_birthdate) bookings = [request_bookings(token, req) for req in booking_requests] prices = extract_prices(bookings) - # create a snapshot of all current prices - dump_all_prices_to_csv(prices) - - # extract the lowest and the last lowest price - new = get_lowest_price(prices) - previous = get_last_price_from_csv() - - # if the price changed, add it to lowest prices - if not previous or new.price != previous.price: - dprint(f"PRICE CHANGE. {previous} -> {new}") - notify_user( - previous - or Price( - "", - "No previous price", - 0.0, - datetime.fromtimestamp(0), - datetime.fromtimestamp(0), - ), - new, - NOTIFICATION_CHANNEL, - ) - add_to_csv(new) + return prices ## CLI @@ -288,21 +281,90 @@ app = typer.Typer() @app.command() -def search( +def main( + travel_date: Annotated[ + str, typer.Argument(help="Travel day to search from. (YYYY-MM-DD)") + ], start_station: int = typer.Option( START_STATION, help="Departure station number. (default: Berlin Hbf)" ), end_station: int = typer.Option( END_STATION, help="Destination station number. (default: Paris Est)" ), - travel_date: str = typer.Option(help="Travel day to search from. (YYYY-MM-DD)"), + birthdate: str = typer.Option(TRAVELLER_BIRTHDATE.strftime("%Y-%m-%d"), help="Traveller birthdate, may be important for discounts. (YYYY-MM-DD)"), + notification_channel: str = typer.Option( + NOTIFICATION_CHANNEL, help="ntfy channel to inform user on." + ), + monitor_mode: bool = typer.Option( + True, + help="Run queries repeatedly over time. If False only runs a single query (oneshot mode).", + ), + monitor_frequency: int = typer.Option( + MONITOR_FREQUENCY, + help="How often to run price queries if in monitoring mode, in seconds.", + ), + base_output_directory: Path = typer.Option( + Path(BASE_DIR), help="Directory in which to output all result files." + ), + lowest_prices_filename: str = typer.Option( + CSV_LOWEST_FILE, help="Filename for collecting lowest found prices." + ), + price_snapshot_pattern: str = typer.Option( + CSV_ALL_PRICES_PATTERN, + help="Filename pattern for saving all prices of each query. Takes %%DATE%% as pattern to replace with current unix timestamp.", + ), + dump_price_snapshot: bool = typer.Option( + True, help="Dump _all_ queried prices into a timestamped csv file." + ), ): + base_output_directory.mkdir(exist_ok=True, parents=True) + lowest_prices_path = base_output_directory.joinpath(lowest_prices_filename) + price_snapshot_path = base_output_directory.joinpath(price_snapshot_pattern) + try: - date_obj = datetime.strptime(travel_date, "%Y-%m-%d") + travel_date_obj = datetime.strptime(travel_date, "%Y-%m-%d") + birth_date_obj = datetime.strptime(birthdate, "%Y-%m-%d") except ValueError: - typer.echo(f"Invalid date format: {travel_date}. Use YYYY-MM-DD", err=True) + typer.echo("Invalid date format. Use YYYY-MM-DD", err=True) raise typer.Exit(1) - main(start_station=start_station, end_station=end_station, travel_date=date_obj) + + while True: + prices = query( + start_station=start_station, end_station=end_station, travel_date=travel_date_obj, traveller_birthdate=birth_date_obj, + ) + + # create a snapshot of all current prices + if dump_price_snapshot: + dump_all_prices_to_csv(prices, price_snapshot_path) + + # extract the lowest and the last lowest price + new = get_lowest_price(prices) + previous = get_last_price_from_csv(lowest_prices_path) + + # if the price changed, add it to lowest prices + if not previous or new.price != previous.price: + dprint(f"PRICE CHANGE. {previous} -> {new}") + add_to_csv(new, lowest_prices_path) + notify_user( + previous + or Price( + "", + "No previous price", + 0.0, + datetime.fromtimestamp(0), + datetime.fromtimestamp(0), + ), + new, + notification_channel, + ) + + # oneshot exit + if not monitor_mode: + break + dprint( + f"Query complete. Monitoring mode active, sleeping for {monitor_frequency} seconds..." + ) + sleep(monitor_frequency) if __name__ == "__main__": diff --git a/uv.lock b/uv.lock index e929183..5ffcdf0 100644 --- a/uv.lock +++ b/uv.lock @@ -94,9 +94,9 @@ wheels = [ ] [[package]] -name = "nj-api" +name = "nightjetter" version = "0.1.0" -source = { virtual = "." } +source = { editable = "." } dependencies = [ { name = "requests" }, { name = "typer" },