Dokumentation

Konventionen

Antwortform, Paginierung, Fehler, Limits und Versionierung — für alle Endpunkte gleich, damit Sie es nur einmal umsetzen.

Antwortform

Erfolg und Fehler sehen bewusst unterschiedlich aus.

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.

Erfolg (Liste)JSON
{
  "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.

FehlerJSON
{
  "ok": false,
  "error": { "code": "INSUFFICIENT_SCOPE", "message": "…" },
  "requestId": "a1b2c3d4"
}

Paginierung

Cursor statt Offset — so verschiebt sich nichts, wenn parallel geschrieben wird.
limit
Seitengröße, 1–200. Ohne Angabe 50.
cursor
Der Wert aus nextCursor der vorigen Seite. Beim ersten Aufruf weglassen.
nextCursor
Steht in jeder Antwort. null bedeutet: 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.

SchleifeJavaScript
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

Pro Schlüssel, nicht pro IP — mehrere Server mit demselben Schlüssel teilen sich das Budget.

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

Drei Formate, jedes mit fester Bedeutung.
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

Was wir jederzeit ändern dürfen — und was nicht.

/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.