From 01d992fd4cd122814d7c1f10fffa06a01ae04ae9 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 12 Aug 2025 12:03:47 +0200 Subject: [PATCH 01/10] Add csv typer options to cli --- main.py | 126 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 80 insertions(+), 46 deletions(-) diff --git a/main.py b/main.py index c5f406c..4ad5de1 100644 --- a/main.py +++ b/main.py @@ -10,8 +10,8 @@ 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 @@ -193,22 +193,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 +233,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,27 +260,71 @@ 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) -> list[Price]: token = request_init_token() connections = request_connections(token, start_station, end_station, travel_date) booking_requests = connection_data_to_booking_requests(connections) bookings = [request_bookings(token, req) for req in booking_requests] prices = extract_prices(bookings) + return prices + + +## CLI +app = typer.Typer() + + +@app.command() +def main( + 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)"), + notification_channel: str = typer.Option( + NOTIFICATION_CHANNEL, help="ntfy channel to inform user on." + ), + 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") + except ValueError: + typer.echo(f"Invalid date format: {travel_date}. Use YYYY-MM-DD", err=True) + raise typer.Exit(1) + prices = query( + start_station=start_station, end_station=end_station, travel_date=date_obj + ) + # create a snapshot of all current prices - dump_all_prices_to_csv(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() + 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( @@ -278,31 +335,8 @@ def main(start_station: int, end_station: int, travel_date: datetime): datetime.fromtimestamp(0), ), new, - NOTIFICATION_CHANNEL, + notification_channel, ) - add_to_csv(new) - - -## CLI -app = typer.Typer() - - -@app.command() -def search( - 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)"), -): - try: - date_obj = datetime.strptime(travel_date, "%Y-%m-%d") - except ValueError: - typer.echo(f"Invalid date format: {travel_date}. Use YYYY-MM-DD", err=True) - raise typer.Exit(1) - main(start_station=start_station, end_station=end_station, travel_date=date_obj) if __name__ == "__main__": From 7390d1febd28bb77f05fd75bbf9a06c4debc0fa0 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 12 Aug 2025 12:08:38 +0200 Subject: [PATCH 02/10] Add monitor mode switch and monitor frequency --- main.py | 74 ++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/main.py b/main.py index 4ad5de1..19ac3fa 100644 --- a/main.py +++ b/main.py @@ -3,6 +3,7 @@ import json from dataclasses import dataclass from datetime import datetime from pathlib import Path +from time import sleep from typing import Any import requests @@ -16,7 +17,8 @@ NOTIFICATION_CHANNEL = "nightjet-price-notifier" START_STATION = "8096003" # BerlinHBF END_STATION = "8796001" # Paris Est -TRAVEL_DATE = "2025-10-14" + +MONITOR_FREQUENCY = 3600 def dprint(txt) -> None: @@ -286,6 +288,14 @@ def main( 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." ), @@ -309,35 +319,45 @@ def main( except ValueError: typer.echo(f"Invalid date format: {travel_date}. Use YYYY-MM-DD", err=True) raise typer.Exit(1) - prices = query( - start_station=start_station, end_station=end_station, travel_date=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, + while True: + prices = query( + start_station=start_station, end_station=end_station, travel_date=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__": app() From 0f3178d9bff8efa037975203b46dc3ce1688fa9b Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 12 Aug 2025 12:25:54 +0200 Subject: [PATCH 03/10] Make travel date required argument --- main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 19ac3fa..6cbd855 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,7 @@ from dataclasses import dataclass from datetime import datetime from pathlib import Path from time import sleep -from typing import Any +from typing import Annotated, Any import requests import typer @@ -278,13 +278,13 @@ app = typer.Typer() @app.command() 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)"), notification_channel: str = typer.Option( NOTIFICATION_CHANNEL, help="ntfy channel to inform user on." ), From 7229ec11a5d34bff5143e52459651482e752f1a5 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 12 Aug 2025 12:32:00 +0200 Subject: [PATCH 04/10] Turn into intallable script package --- pyproject.toml | 10 ++++++++++ src/nj_api/__init__.py | 5 +++++ main.py => src/nj_api/main.py | 4 +++- uv.lock | 2 +- 4 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 src/nj_api/__init__.py rename main.py => src/nj_api/main.py (99%) diff --git a/pyproject.toml b/pyproject.toml index 0fc68a9..dab0a4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,11 +3,21 @@ name = "nj-api" version = "0.1.0" description = "Add your description here" readme = "README.md" +authors = [ + { name = "Marty Oehme", email = "contact@martyoeh.me" } +] requires-python = ">=3.13" dependencies = [ "requests>=2.32.4", "typer>=0.16.0", ] +[project.scripts] +nightjet = "nj_api:main" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + [tool.pyright] typeCheckingMode = "basic" diff --git a/src/nj_api/__init__.py b/src/nj_api/__init__.py new file mode 100644 index 0000000..8b89904 --- /dev/null +++ b/src/nj_api/__init__.py @@ -0,0 +1,5 @@ +from nj_api import main as cli + + +def main() -> None: + cli.app() diff --git a/main.py b/src/nj_api/main.py similarity index 99% rename from main.py rename to src/nj_api/main.py index 6cbd855..0a4c208 100644 --- a/main.py +++ b/src/nj_api/main.py @@ -278,7 +278,9 @@ app = typer.Typer() @app.command() def main( - travel_date: Annotated[str, typer.Argument(help="Travel day to search from. (YYYY-MM-DD)")], + 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)" ), diff --git a/uv.lock b/uv.lock index e929183..f9e453b 100644 --- a/uv.lock +++ b/uv.lock @@ -96,7 +96,7 @@ wheels = [ [[package]] name = "nj-api" version = "0.1.0" -source = { virtual = "." } +source = { editable = "." } dependencies = [ { name = "requests" }, { name = "typer" }, From e510f0bbef0f4e82e572b1ae4c13f700457496ac Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 12 Aug 2025 12:43:45 +0200 Subject: [PATCH 05/10] Rename project --- pyproject.toml | 16 +++++++--------- uv.lock | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index dab0a4c..05451aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,16 +1,11 @@ [project] -name = "nj-api" +name = "nightjet-price-notifier" 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" } -] +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_api:main" @@ -21,3 +16,6 @@ build-backend = "hatchling.build" [tool.pyright] typeCheckingMode = "basic" + +[tool.hatch.build.targets.wheel] +packages = ["src/nj_api"] diff --git a/uv.lock b/uv.lock index f9e453b..324e6a2 100644 --- a/uv.lock +++ b/uv.lock @@ -94,7 +94,7 @@ wheels = [ ] [[package]] -name = "nj-api" +name = "nightjet-price-notifier" version = "0.1.0" source = { editable = "." } dependencies = [ From ee8a4f69fdc29c677f99ae77db20e26c264b7545 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 12 Aug 2025 12:52:37 +0200 Subject: [PATCH 06/10] Add traveller birthdate option --- src/nj_api/main.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/nj_api/main.py b/src/nj_api/main.py index 0a4c208..b1c62c4 100644 --- a/src/nj_api/main.py +++ b/src/nj_api/main.py @@ -17,6 +17,7 @@ NOTIFICATION_CHANNEL = "nightjet-price-notifier" START_STATION = "8096003" # BerlinHBF END_STATION = "8796001" # Paris Est +TRAVELLER_BIRTHDATE = datetime(1990, 1, 1) # TODO: could randomize a little MONITOR_FREQUENCY = 3600 @@ -63,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] @@ -82,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", @@ -262,10 +266,10 @@ def notify_user(previous: Price, new: Price, channel: str) -> None: ) -def query(start_station: int, end_station: int, travel_date: datetime) -> list[Price]: +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) @@ -287,6 +291,7 @@ def main( end_station: int = typer.Option( END_STATION, help="Destination station number. (default: Paris Est)" ), + 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." ), @@ -317,14 +322,15 @@ def main( 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) while True: prices = query( - start_station=start_station, end_station=end_station, travel_date=date_obj + 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 From d6a87f2b2bcfedfb440e74866f8b52196f0074a6 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 20 Aug 2025 15:51:40 +0200 Subject: [PATCH 07/10] Add Dockerfile --- .dockerignore | 5 +++++ Dockerfile | 31 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile 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"] From 5b387bf9071742841d7248fdeadea4a17f80009f Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 20 Aug 2025 15:55:04 +0200 Subject: [PATCH 08/10] Rename project to nightjetter --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 05451aa..0948c15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "nightjet-price-notifier" +name = "nightjetter" version = "0.1.0" description = "Continuously monitors nightjet travel dates for price fluctuations" readme = "README.md" From 7939fc88608ad1b89bee5c91c071fa0d046f09ee Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 20 Aug 2025 16:14:45 +0200 Subject: [PATCH 09/10] Extract API from README and update it --- README.md | 3602 +-------------------------------------------------- docs/API.md | 3549 ++++++++++++++++++++++++++++++++++++++++++++++++++ uv.lock | 2 +- 3 files changed, 3618 insertions(+), 3535 deletions(-) create mode 100644 docs/API.md 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/uv.lock b/uv.lock index 324e6a2..5ffcdf0 100644 --- a/uv.lock +++ b/uv.lock @@ -94,7 +94,7 @@ wheels = [ ] [[package]] -name = "nightjet-price-notifier" +name = "nightjetter" version = "0.1.0" source = { editable = "." } dependencies = [ From d583b7190ffc48b75a06d116990efc629434eec9 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 20 Aug 2025 16:16:27 +0200 Subject: [PATCH 10/10] Move src directory to be under nj --- pyproject.toml | 4 ++-- src/{nj_api => nj}/__init__.py | 2 +- src/{nj_api => nj}/main.py | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename src/{nj_api => nj}/__init__.py (53%) rename src/{nj_api => nj}/main.py (100%) diff --git a/pyproject.toml b/pyproject.toml index 0948c15..488ab35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ requires-python = ">=3.13" dependencies = ["requests>=2.32.4", "typer>=0.16.0"] [project.scripts] -nightjet = "nj_api:main" +nightjet = "nj:main" [build-system] requires = ["hatchling"] @@ -18,4 +18,4 @@ build-backend = "hatchling.build" typeCheckingMode = "basic" [tool.hatch.build.targets.wheel] -packages = ["src/nj_api"] +packages = ["src/nj"] diff --git a/src/nj_api/__init__.py b/src/nj/__init__.py similarity index 53% rename from src/nj_api/__init__.py rename to src/nj/__init__.py index 8b89904..ab66342 100644 --- a/src/nj_api/__init__.py +++ b/src/nj/__init__.py @@ -1,4 +1,4 @@ -from nj_api import main as cli +from nj import main as cli def main() -> None: diff --git a/src/nj_api/main.py b/src/nj/main.py similarity index 100% rename from src/nj_api/main.py rename to src/nj/main.py