API-Nutzung

Inhaltsverzeichnis

Diese Anleitung erklärt, wie Sie eine App erstellen, einen API-Token generieren und damit auf die Transit-Daten-API zugreifen.

Überblick

Die API verwendet Bearer-Token-Authentifizierung. Um auf Daten zuzugreifen, benötigen Sie:

  1. Eine App — von einem Admin im CMS angelegt

  2. Einen API-Token — für diese App generiert, mit bestimmten Berechtigungen (Scopes)

Tokens haben das Format trtok_<zufällig> und werden nur einmal bei der Erstellung angezeigt.

Schritt 1: App erstellen

Nur Benutzer mit der Rolle Admin oder Super-Admin können Apps erstellen.

  1. Melden Sie sich im CMS-Adminbereich an.

  2. Navigieren Sie zu System → Apps in der Seitenleiste.

  3. Klicken Sie auf Neu erstellen.

  4. Geben Sie einen Namen für die App ein (z.B. „Mein Dashboard").

  5. Wählen Sie die Organisation, zu der die App gehört.

  6. Wählen Sie unter Scopes die Datenbereiche aus, auf die die App zugreifen darf (z.B. elevators:read, stop_places:read). Falls Sie keine Scopes auswählen, werden die Standardwerte elevators:read, status_spans:read und stop_places:read gesetzt.

  7. Klicken Sie auf Speichern.

Beim Speichern einer neuen App wird automatisch ein Standard-API-Token erstellt.

Schritt 2: API-Token kopieren

Nach dem Speichern wird automatisch ein Standard-Token erstellt. Sie können auch zusätzliche Tokens anlegen:

  1. Öffnen Sie die App im CMS.

  2. Klicken Sie auf API-Token erstellen.

  3. Geben Sie einen Namen für den Token ein (z.B. „Produktion v1").

  4. Optional: Schränken Sie den Token auf eine Teilmenge der App-Scopes ein.

  5. Klicken Sie auf Erstellen.

  6. Kopieren Sie den Token-Wert sofort — er wird nur einmal angezeigt und kann danach nicht mehr abgerufen werden.

Der Token-Wert sieht beispielsweise so aus:

trtok_a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6q7R8s9T0u1V2

Schritt 3: Token verwenden

Fügen Sie den Token als Bearer-Token im Authorization-Header Ihrer HTTP-Anfragen hinzu.

curl

curl -H "Authorization: Bearer trtok_IHR_TOKEN_HIER" \
     "https://transit.accessibility.cloud/api/elevators?limit=10"

Node.js (fetch)

const response = await fetch(
  "https://transit.accessibility.cloud/api/elevators?limit=10",
  {
    headers: { Authorization: "Bearer trtok_IHR_TOKEN_HIER" },
  },
);
const data = await response.json();
console.log(data);

Python

import requests

response = requests.get(
    "https://transit.accessibility.cloud/api/elevators?limit=10",
    headers={"Authorization": "Bearer trtok_IHR_TOKEN_HIER"},
)
data = response.json()
print(data)

Verfügbare API-Collections

Die API folgt den REST-Konventionen von Payload CMS. Daten-Collections, die Sie abfragen können:

Collection

Endpunkt

Typischer Scope

Aufzüge

/api/elevators

elevators:read

Status-Zeiträume

/api/status-spans

status_spans:read

Haltestellen

/api/stop-places

stop_places:read

Kartenmarker

/api/map-markers

map_markers:read

Netzpläne

/api/network-maps

network_maps:read

GTFS-Haltestellen

/api/gtfs-stops

gtfs:read

GTFS-Linien

/api/gtfs-routes

gtfs:read

Abfrageparameter

Alle Collections unterstützen Standard-Payload-Abfrageparameter:

  • limit — Anzahl der Ergebnisse pro Seite (Standard: 10). Bei größeren Werten bzw. wenn alle Einträge aufgelistet werden sollen, empfiehlt sich eine Anfrage nach einem minimalen Set bestimmter Felder (siehe select in der folgenden Liste), um die Antworten klein zu halten.

  • page — Seitennummer

  • sort — Feldname zum Sortieren (Prefix - für absteigend)

  • where — Filterbedingungen (siehe Beispiele unten)

  • depth — Wie viele Ebenen verknüpfter Dokumente aufgelöst werden (Standard: 1)

  • populate[…][…]=true — Löst verknüpfte Dokumente mit bestimmten Feldnamen auf

  • select[…][…]=true — Bestimmt eine eigene Menge von Feldern, deren Werte in der Antwort enthalten sein sollen

Filterbeispiel — Aufzüge, die außer Betrieb sind:

/api/elevators?where[operational_status.operational_status][equals]=out_of_service&limit=5

Sortierbeispiel — Zuletzt aktualisierte Haltestellen:

/api/stop-places?sort=-updatedAt&limit=20

Filtern mit where

Der Parameter where filtert Ergebnisse nach Feldwerten. In der URL steht der Feldpfad im ersten Klammerpaar und der Operator im zweiten:

where[<Feldpfad>][<Operator>]=<Wert>

Feldpfade können mit Punktnotation in Gruppen und verknüpfte Dokumente hineinreichen — siehe Verschachtelte Feldpfade unten.

Vergleichsoperatoren

Operator

Bedeutung

equals

Wert muss exakt übereinstimmen

not_equals

Wert darf nicht übereinstimmen

greater_than

Für Zahlen- oder Datumsfelder

greater_than_equal

Für Zahlen- oder Datumsfelder

less_than

Für Zahlen- oder Datumsfelder

less_than_equal

Für Zahlen- oder Datumsfelder

like

Ignoriert Groß-/Kleinschreibung. Bei mehreren Wörtern müssen alle Wörter vorkommen, in beliebiger Reihenfolge

not_like

Gegenteil von like

contains

Wert muss als Teilzeichenkette enthalten sein, ignoriert Groß-/Kleinschreibung

in

Wert muss in einer kommagetrennten Liste vorkommen

not_in

Wert darf nicht in einer kommagetrennten Liste vorkommen

exists

Feld hat einen Wert (true) oder hat keinen Wert (false)

near

Nur Point-Felder — siehe Geo-Abfragen

within

Nur Point-Felder — Punkt liegt innerhalb einer GeoJSON-Geometrie

intersects

Nur Point-Felder — Punkt schneidet eine GeoJSON-Geometrie

Beispiele:

Aufzüge, deren Status nicht in_service ist (also außer Betrieb oder unbekannt). Das Feld operational_status ist ein Enum mit genau drei Werten: in_service, out_of_service und unknown.

/api/elevators?where[operational_status.operational_status][not_equals]=in_service

Aufzüge, die außer Betrieb oder unbekannt sind (in erwartet eine kommagetrennte Liste):

/api/elevators?where[operational_status.operational_status][in]=out_of_service,unknown

Haltestellen, die nach einem bestimmten Datum aktualisiert wurden (ISO 8601):

/api/stop-places?where[updatedAt][greater_than]=2026-01-01T00:00:00.000Z

Haltestellen, deren Name eine Zeichenkette enthält (ignoriert Groß-/Kleinschreibung):

/api/stop-places?where[internal_description][contains]=alexanderplatz

Aufzüge, die eine Geoposition haben:

/api/elevators?where[location.centroid][exists]=true

Bedingungen kombinieren: and / or

Mehrere where-Bedingungen auf verschiedenen Feldern werden standardmäßig mit UND verknüpft:

/api/elevators?where[operational_status.operational_status][equals]=out_of_service&where[updatedAt][greater_than]=2026-01-01

Für explizite Bool'sche Logik verwenden Sie and-/or-Arrays mit numerischen Indizes. Diese Abfrage findet Aufzüge, die außer Betrieb oder unbekannt sind:

/api/elevators?where[or][0][operational_status.operational_status][equals]=out_of_service&where[or][1][operational_status.operational_status][equals]=unknown

and und or können beliebig verschachtelt werden. Der obige Query-String entspricht dieser JSON-Struktur:

{
  "or": [
    { "operational_status.operational_status": { "equals": "out_of_service" } },
    { "operational_status.operational_status": { "equals": "unknown" } }
  ]
}

Verschachtelte Feldpfade

Mit Punktnotation fragen Sie Felder innerhalb von Gruppen oder in verknüpften Dokumenten ab:

  • operational_status.operational_status — ein Feld innerhalb einer Gruppe

  • feed.name — ein Feld eines verknüpften Dokuments (die API löst die Verknüpfung automatisch auf)

/api/elevators?where[feed.name][contains]=bvg

Geo-Abfragen auf Point-Feldern

Collections mit Point-Feldern (z.B. centroid bei Haltestellen, location.centroid bei Aufzügen) unterstützen geografische Operatoren:

  • near — Wert ist <Längengrad>,<Breitengrad>,<maxDistanzMeter>,<minDistanzMeter> (die letzten beiden sind optional). Ergebnisse werden nach Entfernung sortiert.

  • within / intersects — Wert ist eine GeoJSON-Geometrie; diese senden Sie am einfachsten über einen generierten Query-String (siehe unten).

Beispiel — Haltestellen im Umkreis von 1 km um den Alexanderplatz:

/api/stop-places?where[centroid][near]=13.4114,52.5219,1000

Komplexe Query-Strings in JavaScript erzeugen

Klammer-Syntax von Hand zu schreiben wird bei größeren Abfragen fehleranfällig. Verwenden Sie das Paket qs-esm, um ein Abfrageobjekt in eine URL umzuwandeln:

import { stringify } from "qs-esm";

const query = {
  or: [
    { "operational_status.operational_status": { equals: "out_of_service" } },
    { "operational_status.operational_status": { equals: "unknown" } },
  ],
};

const queryString = stringify(
  { where: query, limit: 10, depth: 1 },
  { addQueryPrefix: true },
);

const response = await fetch(
  `https://transit.accessibility.cloud/api/elevators${queryString}`,
  { headers: { Authorization: "Bearer trtok_IHR_TOKEN_HIER" } },
);

Performance-Tipps

  • Filtern und sortieren Sie möglichst auf indizierten Feldern (IDs, Slugs, Statusfelder, Zeitstempel).

  • Verwenden Sie select[] und eine niedrige depth, um Antworten klein zu halten.

  • Bevorzugen Sie Paginierung mit limit + page, statt alles auf einmal abzurufen.

Ausgearbeitete Beispiele für diese Muster — Eingrenzung auf ein Verkehrsnetz, Auswahl nur der von der Oberfläche benötigten Felder und lokales Zwischenspeichern von Antworten — finden Sie unter Beispiele: Die API effizient nutzen.

Nur veröffentlichte Daten abrufen (empfohlene Vorgaben)

Um zuverlässig nur veröffentlichte Datensätze zu erhalten, empfehlen wir, folgende Parameter als Standard zu verwenden:

draft=false&locale=de&trash=false&_status=published

  • draft=false — nur veröffentlichte Versionen, keine Entwürfe

  • locale=de — Sprache lokalisierter Felder (de oder en)

  • trash=false — gelöschte (Papierkorb-)Datensätze ausschließen

  • _status=published — explizit auf den Veröffentlichungsstatus filtern

Feldauswahl mit select[]

Mit select[] begrenzen Sie die Antwort auf genau die Felder, die Sie benötigen. Jede Pfadebene wird in eckige Klammern gesetzt; ein Feld wird mit =true eingeschlossen. Das Feld id wird immer zurückgegeben und muss nicht ausgewählt werden.

Beispiel — nur id, die Funktion (function.short_visual) und den Status (operational_status.operational_status) einer Aufzugsliste abrufen:

/api/elevators?draft=false&locale=de&trash=false&_status=published&select[function][short_visual]=true&select[operational_status][operational_status]=true&limit=10

Verknüpfte Felder auflösen: depth und populate[]

  • depth — bestimmt, wie viele Ebenen verknüpfter (Relationship-)Dokumente aufgelöst werden (Standard: 1). Mit depth=0 werden nur die IDs verknüpfter Dokumente geliefert.

  • populate[] — steuert feingranular, welche Felder verknüpfter Dokumente geladen werden. In Kombination mit select[] laden Sie nur die Felder, die Sie benötigen – auch aus tieferen Ebenen.

Beispiel — StopPlaces schlank abrufen und nur die gewünschten Aufzugsfelder auflösen:

/api/stop-places?draft=false&locale=de&trash=false&_status=published&depth=1&select[main_identifier]=true&select[internal_description]=true&select[normalized_names][tokenized_name_db]=true&select[operational_status][elevators]=true&populate[elevators][operational_status][operational_status]=true&populate[elevators][operational_status][status_spans]=true&populate[elevators][function]=true&populate[elevators][feed]=true&populate[elevators][linked_data][owner_org]=true&populate[elevators][linked_data][original_data]=true&limit=10

Join-Felder

Ein Join-Feld ist eine Rückwärtsbeziehung — die Dokumente auf der Gegenseite, die auf dieses Dokument verweisen. Haltestellen legen ihre Kartenmarker auf diese Weise über linked_data.map_markers offen. Ein Join liefert ein paginiertes Objekt, kein flaches Array:

"linked_data": {
  "map_markers": {
    "docs": [ /* Kartenmarker-Objekte, bei depth=0 nur IDs */ ],
    "hasNextPage": false
  }
}

depth steuert, ob ein Join zu Objekten aufgelöst wird:

  • depth=0 — docs enthält die reinen Marker-IDs (z. B. [107]).

  • depth=1 (Standard) oder höher — docs enthält die vollständigen Marker-Objekte (latitude, longitude, map, …).

Beispiel — die Kartenmarker einer Haltestelle mit Koordinaten und Netzplan-ID abrufen:

curl -H "Authorization: Bearer trtok_YOUR_TOKEN_HERE" \
  "https://transit.accessibility.cloud/api/stop-places/42?depth=1&select[linked_data][map_markers]=true"
{
  "id": 42,
  "linked_data": {
    "map_markers": {
      "docs": [
        {
          "id": 107,
          "name": "Alexanderplatz",
          "subject": { "relationTo": "stop-places", "value": 42 },
          "map": 5,
          "coordinate_mode": "world",
          "latitude": 52.5219,
          "longitude": 13.4114
        }
      ],
      "hasNextPage": false
    }
  }
}

Hinweise:

  • map ist die Netzplan-ID. Über den Join bleibt sie bei jedem depth eine ID, da Relationships innerhalb von gejointen Dokumenten nicht weiter aufgelöst werden. Für das Netzplan-Objekt fragen Sie die Marker direkt bei depth=1 ab — GET /api/map-markers?where[subject.value][equals]=42 — dort wird map je Marker zum vollständigen Objekt aufgelöst — oder rufen Sie GET /api/network-maps/5 ab.

  • Eine Relationship wird nur dann zu einem Objekt aufgelöst, wenn depth hoch genug ist und die Scopes des Tokens Lesezugriff auf die Ziel-Collection gewähren; sonst bleibt die ID stehen.

  • Die Anzahl der von einem Join gelieferten Dokumente ist serverseitig begrenzt; hasNextPage zeigt eine Abschneidung an. (Der Payload-Parameter joins hat auf dieser API keine Wirkung.)

  • Um die Haltestelle statt über die numerische ID anhand der DHID zu suchen, nutzen Sie den List-Endpunkt mit ?where[main_identifier][equals]=de:11000:…&limit=1.

Die Feldreferenz zum Kartenmarker finden Sie unter Kartenmarker.

Antwortformat

API-Antworten folgen einer einheitlichen Struktur:

{
  "docs": [
    {
      "id": 1,
      "name": "Aufzug Alexanderplatz",
      "status": "active",
      "updatedAt": "2025-03-15T10:30:00.000Z"
    }
  ],
  "totalDocs": 42,
  "limit": 10,
  "totalPages": 5,
  "page": 1,
  "pagingCounter": 1,
  "hasPrevPage": false,
  "hasNextPage": true,
  "prevPage": null,
  "nextPage": 2
}

Kartenmarker

Ein Kartenmarker platziert ein Subjekt — eine Haltestelle, einen Aufzug, eine Status-Spanne oder eine Organisation — an einer Position auf einem bestimmten Netzplan. Jeder Marker trägt eigene Koordinaten und eine Referenz auf den zugehörigen Netzplan.

Feld

Typ

Beschreibung

id

number

Marker-ID

name

string

Automatisch erzeugtes Label (schreibgeschützt)

subject

relationship

Polymorph — die Entität, auf die der Marker verweist: elevators, stop-places, status-spans oder schema-organizations. Serialisiert als { relationTo, value }; ID bei depth=0.

map

relationship

Der Netzplan, zu dem dieser Marker gehört (network-maps). ID bei depth=0, vollständiges Objekt bei depth>=1.

coordinate_mode

world / display

Bestimmt die Interpretation der Koordinaten (siehe unten)

latitude

number

Breitengrad (im Modus world) oder Anzeige-Y

longitude

number

Längengrad (im Modus world) oder Anzeige-X

Koordinatenmodi:

  • world (Standard) — latitude / longitude sind geografische Koordinaten im Koordinatenreferenzsystem des referenzierten Netzplans.

  • display — latitude / longitude stehen für den Y-/X-Pixelversatz innerhalb der Anzeigefläche des Netzplans (dessen display_coordinates Breite × Höhe), skaliert auf das gerenderte Bild.

Endpunkte:

GET /api/map-markers          # Liste (paginiert)
GET /api/map-markers/{id}     # einzelner Marker

Filterbeispiele:

Alle Marker, die zu einem bestimmten Netzplan gehören:

/api/map-markers?where[map][equals]=5

Alle Marker, deren Subjekt eine bestimmte Haltestelle ist. subject ist polymorph, daher wird auf subject.value (die ID) gefiltert, optional eingeschränkt über subject.relationTo:

/api/map-markers?where[subject.value][equals]=42&where[subject.relationTo][equals]=stop-places

(Ein direkter Filter auf subject — where[subject][equals]=… — wird nicht unterstützt und liefert einen Fehler.)

Um die Kartenmarker einer Haltestelle zusammen mit der Haltestelle in einer einzigen Anfrage abzurufen, nutzen Sie stattdessen den Join linked_data.map_markers — siehe Join-Felder.

Scope: map_markers:read.

Berechtigungs-Scopes

Jeder API-Token hat einen oder mehrere Scopes, die bestimmen, auf welche Collections er zugreifen kann. Schreib-Scopes beinhalten automatisch Lesezugriff.

Scope

Zugriff

elevators:read

Aufzugsdaten lesen

status_spans:read

Status-Zeiträume lesen

stop_places:read

Haltestellendaten lesen

map_markers:read

Kartenmarker lesen

network_maps:read

Netzpläne lesen

gtfs:read

Alle GTFS-Collections lesen

feeds:read

Feed-Definitionen lesen

organizations:read

Organisationsdaten lesen

Token-Verwaltung

Token neu generieren

Falls ein Token kompromittiert wurde, können Sie ihn neu generieren:

  1. Öffnen Sie den API-Token im CMS.

  2. Klicken Sie auf Token neu erzeugen.

  3. Bestätigen Sie die Aktion — der alte Token funktioniert sofort nicht mehr.

  4. Kopieren Sie den neuen Token-Wert.

Token löschen

Löschen Sie das API-Token-Dokument im CMS. Der Token wird sofort widerrufen.

Fehlerantworten

Statuscode

Bedeutung

403 Forbidden

Fehlender Token, ungültiger Token oder Token ohne die erforderliche Berechtigung

404 Not Found

Ressource existiert nicht

400 Bad Request

Ungültige Abfrageparameter

Hinweis: Ein fehlender oder ungültiger Token liefert 403 (nicht 401) — die API behandelt jede nicht authentifizierte Anfrage als „forbidden".

Sicherheitshinweise

  • Tokens werden vor der Speicherung gehasht (SHA-256) — wir speichern niemals Klartext-Tokens.

  • Übertragen Sie Tokens immer über HTTPS.

  • Verwenden Sie die restriktivsten Scopes, die für jede App möglich sind.

  • Erneuern Sie Tokens, wenn Sie einen Leak vermuten.