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:
- Weniger Datensätze: auf ein Verkehrsnetz / eine Region und auf tatsächlich angezeigte Datensätze eingrenzen.
- Weniger Felder — mit
select[]nur die Felder zurückgeben, die gerendert werden sollten. - 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=falseDie 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=falseDas 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=falseNur 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=falseNur 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]=trueIm 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]=trueFeld |
| Beispielwert (Aufzug |
|---|---|---|
| (immer enthalten) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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_serviceoderunknown.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 mitdepth=1oder höher — und mit dem Scopestatus_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]=trueWas pro Status anzeigen
Wenn kein current_status_span vorhanden ist, sollte im Client ein einfacher Text angezeigt werden:
|
| Englisch | Deutsch |
|---|---|---|---|
| In service | In Betrieb | |
|
| Operation paused | Betriebspause |
| Out of service | Außer Betrieb | |
| 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 |
|---|---|
| Der Aufzug wird momentan gewartet. |
| Es gibt einen technischen Ausfall. |
| Der Aufzug ist noch im Bau. |
| Der Aufzug wird gerade abgebaut. |
| Der Aufzug wird saniert/erneuert/ausgetauscht. |
| Der Aufzug ist durch Gewalt beschädigt worden. |
| Der Aufzugsensor liefert keinen klaren Zustand bzw. der gemessene Zustand ändert sich zu oft innerhalb kurzer Zeit, um eine zuverlässige Aussage zu treffen. |
| Der Aufzug wird momentan nicht aktiv durch Sensorik oder Personal beobachtet. |
| 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 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 baseSiehe auch
- API-Nutzung — Authentifizierung, Endpunkte, Abfrageoperatoren,
select[]/populate[]/depth, Join-Felder und Kartenmarker.