API für Tankstellenpreise

Die Preise der Tankstellen-Ampel gibt es auch als JSON-Schnittstelle, für Entwickler und Firmenkunden. Eine Abfrage liefert alle Stationen im Umkreis einer Position, mit Preisen für E5, E10 und Diesel.

Überblick

Die API hat einen einzigen Endpunkt. Anfragen und Antworten sind JSON, die Verbindung läuft ausschließlich über HTTPS. Jede Anfrage braucht einen API-Key im Header X-API-Key. Der Zugang ist ein kostenpflichtiges Angebot der RuppiLab, die Preise nennen wir auf Anfrage, siehe API-Zugang.

Die Preisdaten stammen von der Markttransparenzstelle für Kraftstoffe (MTS-K) beim Bundeskartellamt. Wer sie anzeigt, muss die Quelle nennen, siehe Quellenangabe.

Authentifizierung

Der Key wird bei jeder Anfrage im HTTP-Header X-API-Key mitgeschickt. Fehlt er oder ist er ungültig, antwortet die API mit HTTP 403.

Key nicht veröffentlichen. Ein Key, der im Browser-Code steht, ist für jeden sichtbar. Für Webseiten empfiehlt sich deshalb ein eigener Key mit engen Limits. Für alles andere ruft der eigene Server die API auf.

Endpunkt

POSThttps://api.tankstellen-ampel.de/v1/api.php

Der Anfragekörper ist JSON mit dem Header Content-Type: application/json.

Anfrage

Der Körper enthält die Suchpositionen und die Art der Umkreissuche.

FeldTypPflichtBeschreibung
positionsArrayjaEine oder mehrere Suchpositionen. Die erlaubte Anzahl begrenzt positions_limit des Keys.
positions[].idStringneinFrei wählbare Kennung der Position. Optional. Sie kommt in der Antwort als position_id zurück.
positions[].latZahljaBreitengrad in Dezimalgrad (WGS 84), zum Beispiel 52.75.
positions[].lonZahljaLängengrad in Dezimalgrad (WGS 84), zum Beispiel 13.24.
positions[].distanceZahlneinSuchumkreis in Kilometern, Standard 5, größer als 0. Liegt er über distance_limit des Keys, kürzt die API ihn auf das erlaubte Maximum und ergänzt im Eintrag der Position das Feld warning, statt die Anfrage abzuweisen.
filter_typeStringneinArt der Umkreissuche, Standard bounding_box. In beiden Fällen enthält die Antwort nur Stationen innerhalb des Kreises um die Position, nach Entfernung sortiert. Jeder andere Textwert wird wie radius behandelt.

Beispiel für einen Anfragekörper

{"positions":[{"id":"search","lat":52.75,"lon":13.24,"distance":10}],"filter_type":"bounding_box"}

Limits und Fehler

Jeder Key hat eigene Limits. Sie richten sich nach dem vereinbarten Tarif und werden bei der Einrichtung des Zugangs festgelegt.

LimitBedeutung
positions_limitHöchstzahl der Positionen in einer Anfrage. Wird individuell je Key festgelegt.
distance_limitGrößte erlaubte Distanz in Kilometern je Position. Wird individuell je Key festgelegt.
TageslimitHöchstzahl der Anfragen pro Tag und Key. Es zählt jede gültige Anfrage einmal, unabhängig von der Zahl der Positionen, und setzt sich um Mitternacht (Europe/Berlin) zurück. Ist es erreicht, antwortet die API mit HTTP 429.

Statuscodes

HTTP-StatusBedeutungAntwortkörper
200Anfrage erfolgreich. Die Antwort ist ein Array mit einem Eintrag je Position.Array
400Ungültige Anfrage, etwa ein fehlendes oder falsch typisiertes Feld.{"error": "…"}
403Zugriff verweigert, zum Beispiel weil der Key fehlt oder ungültig ist.{"error": "…"}
429Das Tageslimit des Keys ist erreicht. Weitere Anfragen sind erst wieder möglich, wenn das Limit zurückgesetzt wird.{"error": "…"}
405Falsche HTTP-Methode. Die API akzeptiert POST.{"error": "…"}
413Der Anfragekörper ist zu groß.{"error": "…"}
500Interner Fehler. Anfrage später wiederholen.{"error": "…"}
503Der Dienst ist vorübergehend nicht verfügbar. Anfrage später wiederholen.{"error": "…"}

Fehlerantworten enthalten immer ein Feld error mit einer Beschreibung.

HTTP/1.1 429 Too Many Requests
Content-Type: application/json

{"error": "…"}

Antwort

Bei Erfolg liefert die API ein Array mit einem Eintrag je Position der Anfrage. Preise stehen in Euro je Liter. Zeitstempel in last_change sind ISO 8601 mit Z oder Zeitzonen-Offset.

Eintrag je Position

FeldTypBeschreibung
position_idStringDie id der Position aus der Anfrage.
latZahlBreitengrad der Position.
lonZahlLängengrad der Position.
requested_kmZahlAngefragte Distanz in Kilometern.
radius_kmZahlVerwendeter Suchradius in Kilometern.
station_countGanzzahlAnzahl der Stationen in stations.
execution_time_msZahlVerarbeitungsdauer auf dem Server in Millisekunden.
stationsArrayDie gefundenen Tankstellen, siehe nächste Tabelle.

Felder je Station

FeldTypEinheit / FormatBeschreibung
uuidStringUUIDEindeutige Kennung der Station.
nameStringName der Station.
brandStringMarke, zum Beispiel ARAL. Kann leer sein.
addressStringStraße, Hausnummer, Postleitzahl und Ort in einer Zeile.
latZahlGrad (WGS 84)Breitengrad der Station.
lonZahlGrad (WGS 84)Längengrad der Station.
distance_kmZahlkmEntfernung zur angefragten Position.
is_openBooleantrue, wenn die Station gerade geöffnet ist. Sind die Öffnungszeiten unbekannt, ist der Wert false.
next_openingString oder nullYYYY-MM-DD HH:MM:SSNächste Öffnung einer geschlossenen Station, ohne Zeitzonenangabe als Ortszeit der Öffnungszeiten.
closes_soonGanzzahl oder nullMinutenMinuten bis zur Schließung, gesetzt bis 60 Minuten vor Ladenschluss, sonst null. Die Website zeigt den Hinweis nur bei Werten bis 30.
opening_hoursStringeine ZeileÖffnungszeiten als Text in einer Zeile, Zeiträume durch Komma getrennt.
raw_openingtimes_jsonStringJSON als TextRohdaten der Öffnungszeiten. Für die Anzeige ist opening_hours gedacht.
prices.e5Zahl€ je Liter, 3 NachkommastellenPreis für Super E5. 0 bedeutet: Die Station bietet die Sorte nicht an.
prices.e10Zahl€ je Liter, 3 NachkommastellenPreis für Super E10. 0 bedeutet: Sorte nicht angeboten.
prices.dieselZahl€ je Liter, 3 NachkommastellenPreis für Diesel. 0 bedeutet: Sorte nicht angeboten.
prices.last_change.e5StringISO 8601, kann null seinZeitpunkt der letzten Preisänderung für E5, zum Beispiel 2026-10-17T20:14:00Z.
prices.last_change.e10StringISO 8601, kann null seinZeitpunkt der letzten Preisänderung für E10.
prices.last_change.dieselStringISO 8601, kann null seinZeitpunkt der letzten Preisänderung für Diesel.

Preis 0 heißt: Sorte nicht angeboten. Solche Preise gehören weder angezeigt noch in Durchschnitte oder Vergleiche. Die Tankstellen-Ampel blendet die Station für diese Sorte aus.

Beispielantwort, gekürzt auf eine Station

[
  {
    "position_id": "search",
    "lat": 52.75,
    "lon": 13.24,
    "radius_km": 12,
    "requested_km": 12,
    "station_count": 20,
    "execution_time_ms": 17.61,
    "stations": [
      {
        "uuid": "005056ba-7cb6-1ed2-bceb-8c7d89a0cd33",
        "name": "star Tankstelle",
        "brand": "STAR",
        "address": "Berliner Straße 73, 16515 Oranienburg",
        "lat": 52.744668,
        "lon": 13.237392,
        "distance_km": 0.62,
        "is_open": true,
        "next_opening": null,
        "closes_soon": 30,
        "opening_hours": "Mo–So: 02:00–24:00",
        "raw_openingtimes_json": "[…]",
        "prices": {
          "e5": 2.109,
          "e10": 2.049,
          "diesel": 2.179,
          "last_change": {
            "e5": "2026-10-17T20:14:00Z",
            "e10": "2026-10-17T20:14:00Z",
            "diesel": "2026-10-17T15:54:00Z"
          }
        }
      }
    ]
  }
]

Beispiele

In allen Beispielen steht IHR_API_KEY für den eigenen Key.

curl

curl -X POST "https://api.tankstellen-ampel.de/v1/api.php" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: IHR_API_KEY" \
  -d '{"positions":[{"id":"search","lat":52.75,"lon":13.24,"distance":10}],"filter_type":"bounding_box"}'

PHP

<?php
$payload = [
    'positions'   => [['id' => 'search', 'lat' => 52.75, 'lon' => 13.24, 'distance' => 10]],
    'filter_type' => 'bounding_box',
];

$ch = curl_init('https://api.tankstellen-ampel.de/v1/api.php');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'X-API-Key: IHR_API_KEY',
    ],
    CURLOPT_POSTFIELDS     => json_encode($payload),
]);
$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);
if ($status !== 200) {
    exit('Fehler ' . $status . ': ' . ($data['error'] ?? 'unbekannt'));
}
foreach ($data[0]['stations'] as $s) {
    echo $s['brand'], ' ', $s['prices']['e10'], " €/l\n";
}

JavaScript

const res = await fetch('https://api.tankstellen-ampel.de/v1/api.php', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'X-API-Key': 'IHR_API_KEY' },
  body: JSON.stringify({
    positions: [{ id: 'search', lat: 52.75, lon: 13.24, distance: 10 }],
    filter_type: 'bounding_box'
  })
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || 'HTTP ' + res.status);

for (const s of data[0].stations) {
  console.log(s.brand, s.prices.e10, '€/l');
}

Ampel-Einordnung

Die Antwort enthält keine fertige Einordnung. Die Website berechnet sie aus den Preisen, und die gleiche Regel steht hier für eigene Anwendungen. Gezählt werden nur geöffnete Stationen mit einem Preis über 0 für die gewählte Sorte. Der niedrigste dieser Preise ist der Maßstab, getrennt je Sorte.

  • Günstig Preis höchstens 1,0 ct über dem Tiefstpreis
  • Mittel Preis höchstens 3,0 ct über dem Tiefstpreis
  • Teuer alles darüber

Die Differenz wird auf 0,1 ct gerundet, damit Rundungsfehler bei Gleitkommazahlen nicht die Grenze verschieben.

// Ampel-Einordnung, wie sie die Website verwendet
const open = stations.filter(s => s.is_open && s.prices.e10 > 0);
const min  = Math.min(...open.map(s => s.prices.e10));

function ampel(price) {
  const diffCent = Math.round((price - min) * 100 * 10) / 10; // auf 0,1 ct gerundet
  if (diffCent <= 1.0) return 'günstig';
  if (diffCent <= 3.0) return 'mittel';
  return 'teuer';
}

Quellenangabe

Die Preisdaten stammen von der MTS-K. Wer sie darstellt, muss überall dort, wo Preise stehen, die Quelle nennen:

Preisdaten: Markttransparenzstelle für Kraftstoffe (MTS-K), Bundeskartellamt

Preisänderungen erreichen die MTS-K von den Tankstellen und können deshalb mit Verzögerung in den Daten erscheinen. Das Feld prices.last_change zeigt, wann sich ein Preis zuletzt geändert hat.

API-Zugang

Die Nutzung der API ist kostenpflichtig. Der Preis richtet sich nach Einsatzzweck und Abfragemenge und wird individuell vereinbart: Preis auf Anfrage. Bitte schreiben Sie uns kurz, wofür Sie die Daten nutzen möchten und wie viele Abfragen Sie erwarten. Einen Key gibt es nach Vereinbarung.

mail@ruppilab.de