MDS 2.0 Provider-Einrichtung
Levy Fleets implementiert die Mobility Data Specification (MDS) 2.0 Provider-API vollstaendig. Alle Endpunkte liegen unter /api/mds/[subaccountId]/provider/v2/... und werden mit RS256-JWTs signiert, deren oeffentliche Schluessel pro Subaccount unter .well-known/jwks.json veroeffentlicht werden.
Spec-Version
Wir setzen auf MDS 2.0.1. Das version-Feld im Response-Envelope meldet 2.0.1, und der Content-Type-Header ist application/vnd.mds+json;version=2.0. Staedte, die einen mds-provider-validator gegen diese Basis laufen lassen, sollten ohne Schema-Warnungen bestehen.
Endpunkt-Katalog
Alle Routen haben das Praefix /api/mds/{subaccountId}/provider/v2/. Die subaccountId ist die UUID des Betreiber-Subaccounts (bei den meisten Betreibern einer pro Stadt).
| Endpunkt | Methode | Zweck | Paginierung | Cache |
|---|---|---|---|---|
/vehicles | GET | Alle dem Subaccount zugeordneten Fahrzeuge | keine (eine Seite) | 60s Edge |
/vehicles/status | GET | Aktueller Zustand pro Fahrzeug (eine Zeile pro Fahrzeug) | Cursor | 60s Edge |
/vehicles/{device_id} | GET | Einzelfahrzeug-Detail | n/a | keine |
/trips | GET | Fahrten, die im ?end_time-Fenster enden | Cursor nach ended_at | keine |
/events | GET | Statusaenderungs-Ereignisse im Fenster | Cursor nach event_time | keine |
/telemetry/{vehicle_id} | GET | GPS-/Batterieproben fuer Fenster | keine | keine |
/stops | GET | Vom Betreiber definierte Parkstellplaetze + Ladestopps | keine | 60s Edge |
/reports | GET | Vorab berechnete monatliche Aggregate | keine | 60s Edge |
/.well-known/jwks.json | GET | Oeffentliches JWK-Set zur Verifikation der Response-JWTs | n/a | 60s Edge |
Fahrzeugliste -- /vehicles
Gibt jedes mit dem Subaccount verknuepfte Fahrzeug zurueck. Eine Zeile pro device_id.
curl -H "Authorization: Bearer <token>" \
https://fleets.levyelectric.com/api/mds/<subaccountId>/provider/v2/vehicles
Response-Envelope:
{
"version": "2.0.1",
"last_updated": "2026-05-18T12:00:00Z",
"ttl": 60000,
"vehicles": [ /* MDS Vehicle-Objekte */ ]
}
Fahrzeugstatus -- /vehicles/status
Cursor-paginiert. Standard-Seitengroesse ist 100, max 500. Der Cursor kodiert den letzten Update-Zeitstempel + ID, sodass Folgeseiten ueber Schreibvorgaenge hinweg stabil sind.
curl -H "Authorization: Bearer <token>" \
"https://fleets.levyelectric.com/api/mds/<subaccountId>/provider/v2/vehicles/status?cursor=<opaque>&limit=200"
Response enthaelt next_cursor, falls weitere Seiten existieren:
{
"version": "2.0.1",
"last_updated": "2026-05-18T12:00:00Z",
"vehicles_status": [ /* Status-Objekte, enthaelt current_speed_limit_kph */ ],
"next_cursor": "eyJ0aW1lIjoiMjAyNi0wNS0xOFQxMjowMDowMFoiLCJpZCI6IjQyIn0="
}
Das current_speed_limit_kph-Feld jeder Statuszeile spiegelt die niedrigste durchsetzbare Geschwindigkeit an der letzten bekannten Position des Fahrzeugs wider, unter Beruecksichtigung von Betreiberzonen und aktiven Policy-Geofences. Siehe Echtzeit-Geschwindigkeitsdurchsetzung.
Einzelfahrzeug -- /vehicles/{device_id}
Gibt ein MDS Vehicle-Objekt zurueck. Nuetzlich fuer Validatoren, die einzelne Eintraege pruefen, bevor sie die vollstaendige Liste crawlen.
Fahrten -- /trips
Cursor-Paginierung sortiert nach ended_at aufsteigend. Erforderlicher Query-Parameter end_time ist ein 1-Stunden-Fenster in ISO 8601:
GET /trips?end_time=2026-05-18T00:00:00Z
Der Endpunkt gibt Fahrten zurueck, deren ended_at in [end_time, end_time + 1h) faellt. Offene Fahrten werden ausgeschlossen.
Ereignisse -- /events
Cursor-Paginierung sortiert nach event_time aufsteigend. Gleiche end_time-Fenster-Semantik wie /trips. Emittiert Statusaenderungen (available, reserved, on_trip, non_operational, removed usw.).
Telemetrie -- /telemetry/{vehicle_id}
Gibt GPS- + Batterieproben fuer ein einzelnes Fahrzeug in einem Zeitfenster zurueck. Falls die vehicle_telemetry-Tabelle nicht existiert (einige aeltere Subaccounts), greift die Route automatisch auf vehicle_events zurueck.
Stopps -- /stops
Vom Betreiber definierte Parkstellplaetze und Ladestationen, als MDS Stop-Objekte. Enthaelt vehicle_type_capacity gemaess MDS 2.0-Schema.
Berichte -- /reports
Vorab berechnete monatliche Aggregate: Gesamtfahrten, gesamt aktive Fahrzeuge, durchschnittliche Fahrtdistanz, durchschnittliche Fahrtdauer. Nuetzlich fuer die quartalsweise Stadtpruefung ohne /trips zu crawlen.
Authentifizierung
Levy Fleets akzeptiert MDS-Anfragen mit Bearer-Token. Staedte koennen zusaetzlich den signierten JWT verifizieren, den wir bei jeder Antwort zurueckgeben.
Bearer-Tokens
Ausgestellt aus Einstellungen -> API & Integrationen -> MDS-Tokens. Typischerweise ein Token pro Stadt pro Subaccount. Das Token wird bei jeder Anfrage gegen mds_city_tokens geprueft.
Authorization: Bearer <opaque_token>
Signierter JWT bei jeder Antwort
Jede erfolgreiche Antwort enthaelt einen MDS-JWT-Header mit einem RS256-signierten JWT. Die JWT-Payload enthaelt den sha256 des Response-Body, die Subaccount-ID, den Issuer (https://fleets.levyelectric.com) und den Issued-at-Zeitstempel. Staedte, die einen Integritaetsnachweis benoetigen, koennen den JWT gegen die JWKS-URL verifizieren.
Der Signaturschluessel ist die aktive Zeile in mds_jwks_keys fuer den Subaccount. Wird beim ersten Aufruf lazy generiert. Siehe JWKS-Schluesselverwaltung fuer die Rotation.
Optionale HMAC-Body-Signatur
Fuer Staedte, die einen HMAC-Integritaetsnachweis statt (oder zusaetzlich zum) JWT benoetigen, wird der MDS-Signature-Header in der Antwort gesetzt. Das gemeinsame Geheimnis wird pro Stadt-Token-Zeile konfiguriert.
JWKS-Discovery
Das oeffentliche JWK-Set liegt unter:
GET /api/mds/{subaccountId}/.well-known/jwks.json
Response-Form:
{
"keys": [
{
"kty": "RSA",
"kid": "mds-2026-05",
"use": "sig",
"alg": "RS256",
"n": "...",
"e": "AQAB"
}
]
}
Die kid entspricht dem kid-Feld im MDS-JWT-Header, sodass Staedte waehrend einer Rotation den richtigen Schluessel auswaehlen koennen. Wir veroeffentlichen immer den aktiven Schluessel plus alle Schluessel, die noch im Rotations-Karenzfenster sind (Standard 7 Tage).
Wenn eine Stadt die JWKS-URL aufruft, bevor die erste Anfrage signiert wurde, generiert die Route das erste Schluesselpaar lazy. Das bedeutet, dass neue Betreiber keine manuelle Aktion ausfuehren muessen -- das erste Ping von Populus genuegt.
Response-Envelope
Jede MDS 2.0-Antwort verwendet dieselbe aeussere Form:
{
"version": "2.0.1",
"last_updated": "2026-05-18T12:00:00Z",
"ttl": 60000,
"<data_key>": [ /* Array von MDS-Objekten */ ],
"next_cursor": "..."
}
last_updatedist immer RFC3339 UTC (kein POSIX-Epoch -- MDS 2.0 verlangt RFC3339).ttlentspricht demCache-Control: max-age-Header (in Millisekunden -- MDS-Spec nutzt ms, HTTP nutzt s).next_cursorist nur vorhanden, wenn weitere Seiten existieren.
Cursor-Paginierung
Cursors sind opake base64-kodierte JSON { "time": <ISO>, "id": <last_id> }. Sie sind stabil ueber Schreibvorgaenge: Eine neu eingefuegte Zeile nach dem Crawl-Beginn erscheint auf einer kuenftigen Seite, niemals auf dem aktuellen Cursor.
Staedte sollten:
- Initialanfrage ohne
cursorstellen. next_cursoraus der Antwort lesen.- Bei der naechsten Anfrage zurueckgeben:
?cursor=<value>. - Stoppen, wenn
next_cursorfehlt.
Seitengroesse standardmaessig 100. ?limit=<n> zum Ueberschreiben (max 500).
Edge-Caching
Wir cachen /vehicles, /vehicles/status, /stops, /reports und /.well-known/jwks.json am Vercel-Edge fuer 60 Sekunden. Fahrten, Ereignisse und Telemetrie werden nicht gecacht -- sie sind durch Query zeitgebunden, sodass jede Anfrage ohnehin einen eindeutigen Cache-Schluessel hat.
Der Cache-Control: public, s-maxage=60-Header wird auf gecachten Endpunkten automatisch gesetzt.
Rate-Limiting
Jedes Bearer-Token ist auf 60 Anfragen pro Minute mit einem Burst von 600 Anfragen begrenzt. Ueberschreitung gibt HTTP 429 mit Retry-After zurueck. Validator-Crawls (10-15 Endpunkte hintereinander) bleiben deutlich innerhalb des Bursts.
Wenn eine Stadt das Limit konsequent ueberschreitet, erhoehen Sie es ueber Einstellungen -> API & Integrationen -> MDS-Tokens -> Rate-Limit-Override.
Validator-Konfiguration
Der OMF-mds-provider-validator (und das interne Pendant von Populus) erwartet:
| Check | Wert |
|---|---|
| Basis-URL | https://fleets.levyelectric.com/api/mds/<subaccountId>/provider/v2/ |
| Auth | Authorization: Bearer <token> |
| JWKS | https://fleets.levyelectric.com/api/mds/<subaccountId>/.well-known/jwks.json |
| Spec-Version | 2.0.1 |
| Content-Type | application/vnd.mds+json;version=2.0 |
Fuehren Sie den Validator selbst aus, bevor Sie an eine Stadt uebergeben:
mds-provider-validator \
--base "https://fleets.levyelectric.com/api/mds/<subaccountId>/provider/v2" \
--auth "Bearer <token>" \
--version 2.0.1 \
--jwks "https://fleets.levyelectric.com/api/mds/<subaccountId>/.well-known/jwks.json"
Ein sauberer Lauf meldet Passed: all endpoints conform. Etwaige Fehler werden mit dem betroffenen Feld und Endpunkt angezeigt und sollten behoben werden, bevor die URL an eine Stadt geht.
Fehlerbehebung
- 401 bei jeder Anfrage -- Bearer-Token fehlt oder wurde widerrufen. Neues aus Einstellungen ausstellen.
- Validator scheitert am
version-Feld -- bestaetigen, dass der Response-Envelope2.0.1meldet, nicht2.0.0. Die Version ist eine Konstante insrc/lib/mds/v2/response.ts. MDS-JWT-Header fehlt in einer Antwort -- das JWKS-Schluesselpaar konnte nicht generiert werden./api/mds/<subaccountId>/.well-known/jwks.jsondirekt aufrufen, um eine Lazy-Mint zu erzwingen, dann erneut versuchen.current_speed_limit_kphimmer 0 -- das Fahrzeug hat keine letzte bekannte Position, oder keine Betreiberzonen / Policy-Geofences enthalten es. Pruefen, dass eine aktuelle GPS-Probe invehicle_eventsexistiert.
Siehe Fehlerbehebung fuer die vollstaendige Checkliste.
Was kommt als naechstes?
- JWKS-Schluesselverwaltung -- Rotation, kid und Karenzfenster.
- GBFS 3.0-Feeds -- der oeffentliche Feed parallel zur MDS Provider-API.
- Policy-Aufnahme von Staedten -- Regeln aus dem staedtischen Policy-Feed beziehen.