Beispiele: Die API effizient nutzen

Inhaltsverzeichnis

Diese Anleitung sammelt praktische Muster, um die Transit-Daten-API effizient abzufragen — nur die Daten zu laden, die Sie brauchen, nur für das Netz, das Sie interessiert, und diese lokal zwischenzuspeichern.

Authentifizierung, Endpunkte, Operatoren und die vollständige Parameterreferenz finden Sie zuerst in der Anleitung API-Nutzung.

Kleine Server-Antworten → mehr Effizienz

Anfrage sollten möglichst kleine Antworten anfordern:

  1. Weniger Datensätze: auf ein Verkehrsnetz / eine Region und auf tatsächlich angezeigte Datensätze eingrenzen.
  2. Weniger Felder — mit select[] nur die Felder zurückgeben, die gerendert werden sollten.
  3. Weniger Anfragen — Antworten lokal zwischenspeichern und innerhalb eines sinnvollen Zeitfensters wiederverwenden.

Zusätzlich ist es hilfreich, Daten in flachen Strukturen statt verschachtelt abzufragen, Datensätze aus der Antwort nach ID zwischenzuspeichern, und dieses Konzept zu nutzen, um statische und Echtzeitdaten getrennt zu behandeln.

Es ist dabei in Ordnung und eventuell sogar am effizientesten, alle Datensätze einer Sammlung (z.B. alle Aufzüge, alle Betriebszustände, oder alle Haltestellen) auf einmal anzufragen und die Antworten clientseitig zu cachen, solange nur wirklich benötigte Felder abgefragt werden. Dafür kann Client-Code z.B. einen hohen Zahlenwert für den limit-Query-Parameter verwenden (z.B. 10000), um Paginierung zu vermeiden.

Die folgenden Abschnitte beschreiben Möglichkeiten zur Minimierung von Datenvolumen und Anfragefrequenz.

Immer die „Nur veröffentlichte Daten"-Vorgaben anhängen

Clients wie Broken Lifts hängen an jede Anfrage denselben Filter-Suffix an:

&draft=false&locale=de&trash=false

Die Antwort enthält dann nur veröffentlichte, nicht gelöschte/archivierte Datensätze mit lokalisierten Feldern auf Deutsch.

Auf ein Verkehrsnetz eingrenzen

Aufzüge und Haltestellen verzeichnen die Verkehrsnetze, zu denen sie gehören, im Feld linked_data.transit_networks_served — eine Verknüpfung zur Organisation des Netzes. Filtern Sie darauf, um nur die Daten eines Netzes abzurufen. Broken Lifts bedient z.B. nur das VBB-Netz (Verkehrsverbund Berlin-Brandenburg), das die Organisations-ID 1 hat:

# Aufzüge im VBB-Netz
/api/elevators?where[linked_data.transit_networks_served][equals]=1&draft=false&locale=de&trash=false

Das funktioniert auch für Haltestellen:

# Haltestellen im VBB-Netz
/api/stop-places?where[linked_data.transit_networks_served][equals]=1&draft=false&locale=de&trash=false

…und Betriebszustände — hier kann man über die betroffene Haltestelle (site) filtern:

# Status-Spannen für Stationen im VBB-Netz
/api/status-spans?where[site.linked_data.transit_networks_served][equals]=1&draft=false&locale=de&trash=false

Nur Aufzüge abrufen, die eine Haltestellen-Zuordnung haben

Für eine Aufzug-App im ÖPNV sind nur Aufzüge an Haltestellen interessant, aber nicht jeder Aufzug ist notwendigerweise einer Haltestelle zugeordnet. Durch Filtern nach Aufzügen mit existentem location.site -Feld werden Aufzüge außerhalb von Haltestellen in der Antwort weggelassen. Filter können hierfür auch kombiniert weren – hier zum Anzeigen einer Anzahl:

# Alle VBB-Stationsaufzüge zählen
/api/elevators/count?where[linked_data.transit_networks_served][equals]=1&where[location.site][exists]=true&draft=false&locale=de&trash=false

# Nur die defekten zählen
/api/elevators/count?where[linked_data.transit_networks_served][equals]=1&where[operational_status.operational_status][equals]=out_of_service&where[location.site][exists]=true&draft=false&locale=de&trash=false

Nur die Felder anfragen, die im UI angezeigt werden

Am besten ist es, nicht den gesamten Datenbestand an Feldern zu laden, sondern Abfragen serverseitig einzugrenzen.

Der Query-Parameter select[…] begrenzt die Antwort auf genau die Felder, die der Client benötigt — oft schrumpft eine Serverantwort dadurch um ein vielfaches. Der folgende Feed-Abruf holt z.B. nur drei Felder (name, updatedAt, organization.name):

/api/feeds/5?depth=0&draft=false&locale=de&trash=false&select[name]=true&select[updatedAt]=true&select[organization][name]=true

Im UI angezeigte Aufzugsfelder mit echten Werten

Der folgende Aufruf zeigt beispielhafte realistische Feldinhalte (auf deutsch, also mit Locale de) für einen Aufzug an S+U Alexanderplatz Bhf — Aufzug 6850, der die Straßenebene mit der U2, der Passage und dem U5-Bahnsteig Richtung Hönow verbindet.

Beispiel-Anfrage:

/api/elevators?where[linked_data.transit_networks_served][equals]=1&where[location.site][equals]=788&depth=0&draft=false&locale=de&trash=false&select[function][short_visual]=true&select[function][short_tts]=true&select[function][long_tts]=true&select[operational_status][operational_status]=true&select[operational_status][status_spans]=true&select[location][site]=true&select[linked_data][transit_networks_served]=true

Feld

select[]-Pfad

Beispielwert (Aufzug 6850)

id

(immer enthalten)

6850

function.short_visual

select[function][short_visual]=true

Alexanderplatz ↔ U2 ↔ Passage ↔ U5 (→ Hönow)

function.short_tts

select[function][short_tts]=true

zwischen Straße, U2, Passage, und U5 Richtung Hönow

function.long_tts

select[function][long_tts]=true

Zwischen Straße und Bahnsteig U2 und Bahnhofspassage und Bahnsteig U5 Richtung Hönow

operational_status.operational_status

select[operational_status][operational_status]=true

in_service

operational_status.status_spans

select[operational_status][status_spans]=true

{ "docs": [], "hasNextPage": false }

location.site

select[location][site]=true

788 (die Haltestelle S+U Alexanderplatz Bhf)

linked_data.transit_networks_served

select[linked_data][transit_networks_served]=true

[1] (das VBB-Netz — Verkehrsverbund Berlin-Brandenburg)

function.short_visual / short_tts / long_tts sind die menschenlesbaren Bezeichnungen, die Broken Lifts anzeigt und vorliest; operational_status.operational_status steuert das Funktioniert-/Defekt-Abzeichen.

Heruntergeladene Daten lokal zwischenspeichern

Statusdaten ändern sich nur alle paar Minuten, und statische Daten (Feed-Metadaten, Stationsbeschreibungen) ändern sich kaum. Wir empfehlen, Antworten mit zwei unterschiedlichen Time-to-live-Intervallen zu cachen, z.B.: 10 Minuten für statusbehaftete Daten und eine Stunde oder einen Tag für statische Daten. Die Wiederverwendung einer gecachten Antwort vermeidet viele unnötige Anfragen und spart Ressourcen.

Beide Snippets unten zeigen, wir man mit der Standardbibliothek der jeweiligen Sprache Antworten einen Hash der Anfrage-URL cachen kann, speichern { fetchedAt, body } im Cache und laden erst dann neu, wenn der Eintrag älter als die TTL ist.

Die Snippets sind nicht production-ready, verdeutlichen allerdings das Prinzip.

ECMAScript (Node.js ≥ 24, ESM)

import { mkdir, readFile, writeFile } from "node:fs/promises";
import { createHash } from "node:crypto";
import { join } from "node:path";

const CACHE_DIR = ".api-cache";
const DEFAULT_TTL_MS = 10 * 60 * 1000; // 10 Minuten

export async function fetchCached(url, { token, ttlMs = DEFAULT_TTL_MS } = {}) {
  await mkdir(CACHE_DIR, { recursive: true });
  const key = createHash("sha256").update(url).digest("hex");
  const file = join(CACHE_DIR, `${key}.json`);

  try {
    const entry = JSON.parse(await readFile(file, "utf8"));
    if (Date.now() - entry.fetchedAt < ttlMs) {
      return entry.body; // frischer Cache-Treffer — keine Netzanfrage
    }
  } catch {
    /* Cache-Fehltreffer oder unlesbar → weiter zum Netz */
  }

  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${token}` },
  });
  if (!res.ok) throw new Error(`HTTP ${res.status} für ${url}`);
  const body = await res.json();

  await writeFile(file, JSON.stringify({ url, fetchedAt: Date.now(), body }));
  return body;
}

// Verwendung:
const data = await fetchCached(
  "https://transit.accessibility.cloud/api/elevators?where[location.site][equals]=788&locale=de&depth=0&draft=false&trash=false",
  { token: "trtok_IHR_TOKEN_HIER" },
);
console.log(data.docs.length, "Aufzüge");

Python (3.9+)

import hashlib
import json
import time
import urllib.request
from pathlib import Path

CACHE_DIR = Path(".api-cache")
DEFAULT_TTL_SECONDS = 10 * 60  # 10 Minuten

def fetch_cached(url: str, token: str, ttl: int = DEFAULT_TTL_SECONDS):
    CACHE_DIR.mkdir(parents=True, exist_ok=True)
    key = hashlib.sha256(url.encode("utf-8")).hexdigest()
    path = CACHE_DIR / f"{key}.json"

    if path.exists() and time.time() - path.stat().st_mtime < ttl:
        return json.loads(path.read_text("utf-8"))  # frischer Cache-Treffer

    req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"})
    with urllib.request.urlopen(req) as resp:
        body = json.load(resp)

    path.write_text(json.dumps(body), "utf-8")
    return body

# Verwendung:
data = fetch_cached(
    "https://transit.accessibility.cloud/api/elevators"
    "?where[location.site][equals]=788&locale=de&depth=0&draft=false&trash=false",
    token="trtok_IHR_TOKEN_HIER",
)
print(len(data["docs"]), "Aufzüge")

Passen Sie die TTL pro Endpunkt an: ein kurzes Fenster (Minuten) für alles mit operational_status oder status_spans, ein langes (eine Stunde oder mehr) für Feed- und Haltestellen-Metadaten.

Den Status in einen Anzeigetext umwandeln

Zwei Felder bestimmen den Status eines Aufzugs:

  • operational_status.operational_status: in_service, out_of_service oder unknown.
  • operational_status.current_status_span — eine Verknüpfung zu dem Betriebszustand (StatusSpan), der den Status aktuell bestimmt (ein angepinnter Zustand gewinnt, sonst der neueste aktive. Zustände mit Beginn in der Zukunft werden ignoriert). Abruf mit depth=1 oder höher — und mit dem Scope status_spans:read — ermöglicht, zu einem Objekt aufzulösen; sonst erhalten Sie nur seine ID, und er ist leer, wenn kein Zustand aktiv ist (der Normalfall bei einem funktionierenden Aufzug).
/api/elevators/5373?depth=1&draft=false&locale=de&trash=false&select[operational_status][operational_status]=true&select[operational_status][current_status_span]=true

Was pro Status anzeigen

Wenn kein current_status_span vorhanden ist, sollte im Client ein einfacher Text angezeigt werden:

operational_status

reason_type

Englisch

Deutsch

in_service


In service

In Betrieb

out_of_service

== 'operation_paused'

Operation paused

Betriebspause


!= 'operation_paused'

Out of service

Außer Betrieb

unknown


Status unknown

Status unbekannt

Wenn ein current_status_span vorhanden ist, ergänzen Sie den zeitlichen Kontext. Ein Zustand kann in der Zukunft enden — z. B. eine geplante Sperrung mit vorgesehener Wiederinbetriebnahme — daher muss die Formulierung sich anpassen: „… bis {end}", solange das Ende noch bevorsteht, und „… seit {start}", sobald das nicht mehr der Fall ist (oder der Zustand kein Enddatum hat).

Für Aufzug 5373 am Alexanderplatz — out_of_service mit einem Zustand, der am 2026-08-26 endet — liefern die Helfer unten "Außer Betrieb bis 26. Aug. 2026" / "Out of service until Aug 26, 2026". Sobald dieses Enddatum vergangen ist, wechselt derselbe Code zu "Außer Betrieb seit 2. Juli 2026".

Abkürzung: Das schreibgeschützte virtuelle Feld operational_status.current_status_span_title liefert einen fertigen, lokalisierten Text (z.B. die Zeichenkette „Datumsbereich – Beschreibung"), falls der Server die Formatierung übernehmen soll.

reason_type – Warum ist ein Aufzug außer Betrieb?

Das Feld reason_type eines Betriebszustands nennt den Grund dafür, dass ein Aufzug einen Status hat. Diese String-Werte existieren:

Enum-Wert

Bedeutung

maintenance

Der Aufzug wird momentan gewartet.

failure

Es gibt einen technischen Ausfall.

under_construction

Der Aufzug ist noch im Bau.

demolition

Der Aufzug wird gerade abgebaut.

renovation

Der Aufzug wird saniert/erneuert/ausgetauscht.

vandalism

Der Aufzug ist durch Gewalt beschädigt worden.

sensor_jitter

Der Aufzugsensor liefert keinen klaren Zustand bzw. der gemessene Zustand ändert sich zu oft innerhalb kurzer Zeit, um eine zuverlässige Aussage zu treffen.

not_monitored

Der Aufzug wird momentan nicht aktiv durch Sensorik oder Personal beobachtet.

operation_paused

Der Betrieb ist momentan pausiert.

⚠️ Achten Sie besonders auf diesen Wert: Hier liegt keine Störung vor — der Aufzug wird gezielt für einen Teil des Tages abgeschaltet (typischerweise nachts).

Der Wert kommt häufig vor, sein operational_status ist out_of_service. Ein schlichtes „Außer Betrieb“ in der FGI wäre allerdings irreführend.

In Navigations-Apps sollte der Zustand normalerweise nicht fahrgastrelevant sein: Aufzüge werden typischerweise ausgeschaltet, weil die Haltestelle gleichzeitig nicht in Betrieb ist. Ein Aufzug in diesem Status sollte also in berechneten Reiseketten normalerweise nicht vorkommen.

Falls der Aufzugstatus dennoch relevant ist, sollte für den fall einer Betriebspause aber eine genauere Meldung als „Außer Betrieb“ angezeigt werden, um nicht den Eindruck zu erwecken, ein Aufzug sei jede Nacht defekt.

ECMAScript (Node.js ≥ 24)

const LABELS = {
  en: { in_service: "In service", out_of_service: "Out of service", unknown: "Status unknown" },
  de: { in_service: "In Betrieb", out_of_service: "Außer Betrieb", unknown: "Status unbekannt" },
};
const UNTIL = { en: "until", de: "bis" };
const SINCE = { en: "since", de: "seit" };

function elevatorStatusLabel(operationalStatus, locale = "en", now = new Date()) {
  const status = operationalStatus?.operational_status ?? "unknown";
  const base = (LABELS[locale] ?? LABELS.en)[status] ?? LABELS.en.unknown;

  const span = operationalStatus?.current_status_span;
  if (!span || typeof span !== "object") return base; // kein aktiver Zustand → einfache Bezeichnung

  const fmt = new Intl.DateTimeFormat(locale, {
    day: "numeric", month: "short", year: "numeric", timeZone: "Europe/Berlin",
  });
  const end = span.end_date ? new Date(span.end_date) : null;
  const start = span.start_date ? new Date(span.start_date) : null;

  if (end && end.getTime() > now.getTime()) {           // endet in der Zukunft
    return `${base} ${UNTIL[locale] ?? UNTIL.en} ${fmt.format(end)}`;
  }
  if (start) {                                          // läuft seit dem Beginn
    return `${base} ${SINCE[locale] ?? SINCE.en} ${fmt.format(start)}`;
  }
  return base;
}

// const os = elevator.operational_status;      // mit depth >= 1 abgerufen
// elevatorStatusLabel(os, "de");               // → "Außer Betrieb bis 26. Aug. 2026"

Python (3.9+)

from datetime import datetime, timezone
from zoneinfo import ZoneInfo

LABELS = {
    "en": {"in_service": "In service", "out_of_service": "Out of service", "unknown": "Status unknown"},
    "de": {"in_service": "In Betrieb", "out_of_service": "Außer Betrieb", "unknown": "Status unbekannt"},
}
UNTIL = {"en": "until", "de": "bis"}
SINCE = {"en": "since", "de": "seit"}
_MONTHS = {"en": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
           "de": ["Jan.", "Feb.", "März", "Apr.", "Mai", "Juni", "Juli", "Aug.", "Sep.", "Okt.", "Nov.", "Dez."]}

def _fmt(dt, locale):
    d = dt.astimezone(ZoneInfo("Europe/Berlin"))
    mon = _MONTHS.get(locale, _MONTHS["en"])[d.month - 1]
    return f"{mon} {d.day}, {d.year}" if locale == "en" else f"{d.day}. {mon} {d.year}"

def _parse(s):
    return datetime.fromisoformat(s.replace("Z", "+00:00")) if s else None

def elevator_status_label(operational_status, locale="en", now=None):
    now = now or datetime.now(timezone.utc)
    status = (operational_status or {}).get("operational_status") or "unknown"
    base = LABELS.get(locale, LABELS["en"]).get(status, LABELS["en"]["unknown"])

    span = (operational_status or {}).get("current_status_span")
    if not isinstance(span, dict):
        return base  # kein aktiver Zustand → einfache Bezeichnung

    end = _parse(span.get("end_date"))
    start = _parse(span.get("start_date"))
    if end and end > now:                       # endet in der Zukunft
        return f"{base} {UNTIL.get(locale, UNTIL['en'])} {_fmt(end, locale)}"
    if start:                                   # läuft seit dem Beginn
        return f"{base} {SINCE.get(locale, SINCE['en'])} {_fmt(start, locale)}"
    return base

Siehe auch

  • API-Nutzung — Authentifizierung, Endpunkte, Abfrageoperatoren, select[] / populate[] / depth, Join-Felder und Kartenmarker.