Konventionen
Antwortform, Paginierung, Fehler, Limits und Versionierung — für alle Endpunkte gleich, damit Sie es nur einmal umsetzen.
Antwortform
Im Erfolgsfall antworten wir mit den Daten selbst — ohne zusätzliche Hülle. Listen sind immer ein Objekt mit data und nextCursor, Einzelressourcen das Objekt direkt.
{
"data": [ /* … */ ],
"nextCursor": "bkg_5Rt8wY"
}Im Fehlerfall kommt eine einheitliche Hülle mit maschinenlesbarem code und einer requestId. Schicken Sie die bei Rückfragen mit — damit finden wir den Vorgang in unseren Protokollen wieder.
{
"ok": false,
"error": { "code": "INSUFFICIENT_SCOPE", "message": "…" },
"requestId": "a1b2c3d4"
}Paginierung
limit- Seitengröße, 1–200. Ohne Angabe 50.
cursor- Der Wert aus
nextCursorder vorigen Seite. Beim ersten Aufruf weglassen. nextCursor- Steht in jeder Antwort.
nullbedeutet: das war die letzte Seite.
Die Schleife endet also nicht an einer Gesamtzahl — es gibt bewusst keine, weil sie eine zusätzliche Zählabfrage kosten würde —, sondern daran, dass nextCursor leer ist.
let cursor;
do {
const url = new URL('/api/v1/stops', base);
url.searchParams.set('limit', '200');
if (cursor) url.searchParams.set('cursor', cursor);
const page = await get(url);
handle(page.data);
cursor = page.nextCursor;
} while (cursor);Fehlercodes
401 UNAUTHENTICATED- Schlüssel fehlt, ist ungültig, widerrufen oder abgelaufen. Welcher Fall zutrifft, sagen wir absichtlich nicht.
403 INSUFFICIENT_SCOPE- Der Schlüssel trägt das für diesen Endpunkt nötige Recht nicht.
403 NOT_ENTITLED- Der Tarif der Stadt enthält die offenen Schnittstellen nicht.
404 NOT_FOUND- Die angefragte Ressource existiert für diese Stadt nicht — etwa ein Feed-Abruf, obwohl kein aktiver Fahrplan hinterlegt ist.
422 INVALID_QUERY- Ein Query-Parameter ist ungültig. Die Meldung nennt das betroffene Feld.
429 RATE_LIMITED- Zu viele Anfragen in kurzer Zeit — siehe unten.
500 INTERNAL- Unerwarteter Fehler auf unserer Seite. Wiederholen Sie den Aufruf; bleibt es dabei, melden Sie sich mit der requestId.
Rate-Limit
Das Limit arbeitet als Eimer, der sich stetig nachfüllt: bis zu 120 Anfragen kurz hintereinander, danach etwa 2 Anfragen pro Sekunde im Dauerbetrieb. Ist der Eimer leer, antworten wir mit 429.
Für regelmäßige Abgleiche ist ein nächtlicher Lauf über einen Zeitraum sparsamer und zuverlässiger als häufiges Abfragen. Wer zeitnah reagieren muss, nimmt Webhooks.
Zeiten und Zeitzonen
YYYY-MM-DDServicedatum- Der Betriebstag, an dem gefahren wird — etwa das rideDate einer Buchung. Kein Zeitanteil, keine Zeitzone.
HH:MMOrtszeit- Fahrplanzeiten und Abholfenster, immer in Europe/Berlin. Sommer- und Winterzeit sind darin bereits berücksichtigt.
ISO 8601 (UTC)Zeitpunkt- Systemzeitstempel wie createdAt, mit Zeitzonenangabe und in UTC. Zum Anzeigen umrechnen, zum Vergleichen nicht.
Versionierung
/api/v1 ist stabil. Neue optionale Felder, neue Endpunkte und neue Ereignistypen können jederzeit hinzukommen. Ihr Client muss deshalb unbekannte Felder ignorieren, statt an ihnen zu scheitern.
Als Bruch behandeln wir: ein Feld entfernen, seinen Typ ändern, seine Bedeutung umdeuten, ein bislang optionales Feld verpflichtend machen oder einen Endpunkt abschaffen. So etwas erscheint unter einer neuen Versionsnummer im Pfad; die alte bleibt zunächst bestehen und antwortet dann mit einem Deprecation-Header.
Die maschinenlesbare Beschreibung finden Sie jederzeit unter /api/v1/openapi.json — daraus lassen sich Clients generieren.