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
https://api.tankstellen-ampel.de/v1/api.phpDer Anfragekörper ist JSON mit dem Header Content-Type: application/json.
Anfrage
Der Körper enthält die Suchpositionen und die Art der Umkreissuche.
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
positions | Array | ja | Eine oder mehrere Suchpositionen. Die erlaubte Anzahl begrenzt positions_limit des Keys. |
positions[].id | String | nein | Frei wählbare Kennung der Position. Optional. Sie kommt in der Antwort als position_id zurück. |
positions[].lat | Zahl | ja | Breitengrad in Dezimalgrad (WGS 84), zum Beispiel 52.75. |
positions[].lon | Zahl | ja | Längengrad in Dezimalgrad (WGS 84), zum Beispiel 13.24. |
positions[].distance | Zahl | nein | Suchumkreis 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_type | String | nein | Art 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.
| Limit | Bedeutung |
|---|---|
positions_limit | Höchstzahl der Positionen in einer Anfrage. Wird individuell je Key festgelegt. |
distance_limit | Größte erlaubte Distanz in Kilometern je Position. Wird individuell je Key festgelegt. |
| Tageslimit | Hö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-Status | Bedeutung | Antwortkörper |
|---|---|---|
| 200 | Anfrage erfolgreich. Die Antwort ist ein Array mit einem Eintrag je Position. | Array |
| 400 | Ungültige Anfrage, etwa ein fehlendes oder falsch typisiertes Feld. | {"error": "…"} |
| 403 | Zugriff verweigert, zum Beispiel weil der Key fehlt oder ungültig ist. | {"error": "…"} |
| 429 | Das Tageslimit des Keys ist erreicht. Weitere Anfragen sind erst wieder möglich, wenn das Limit zurückgesetzt wird. | {"error": "…"} |
| 405 | Falsche HTTP-Methode. Die API akzeptiert POST. | {"error": "…"} |
| 413 | Der Anfragekörper ist zu groß. | {"error": "…"} |
| 500 | Interner Fehler. Anfrage später wiederholen. | {"error": "…"} |
| 503 | Der 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
| Feld | Typ | Beschreibung |
|---|---|---|
position_id | String | Die id der Position aus der Anfrage. |
lat | Zahl | Breitengrad der Position. |
lon | Zahl | Längengrad der Position. |
requested_km | Zahl | Angefragte Distanz in Kilometern. |
radius_km | Zahl | Verwendeter Suchradius in Kilometern. |
station_count | Ganzzahl | Anzahl der Stationen in stations. |
execution_time_ms | Zahl | Verarbeitungsdauer auf dem Server in Millisekunden. |
stations | Array | Die gefundenen Tankstellen, siehe nächste Tabelle. |
Felder je Station
| Feld | Typ | Einheit / Format | Beschreibung |
|---|---|---|---|
uuid | String | UUID | Eindeutige Kennung der Station. |
name | String | Name der Station. | |
brand | String | Marke, zum Beispiel ARAL. Kann leer sein. | |
address | String | Straße, Hausnummer, Postleitzahl und Ort in einer Zeile. | |
lat | Zahl | Grad (WGS 84) | Breitengrad der Station. |
lon | Zahl | Grad (WGS 84) | Längengrad der Station. |
distance_km | Zahl | km | Entfernung zur angefragten Position. |
is_open | Boolean | true, wenn die Station gerade geöffnet ist. Sind die Öffnungszeiten unbekannt, ist der Wert false. | |
next_opening | String oder null | YYYY-MM-DD HH:MM:SS | Nächste Öffnung einer geschlossenen Station, ohne Zeitzonenangabe als Ortszeit der Öffnungszeiten. |
closes_soon | Ganzzahl oder null | Minuten | Minuten bis zur Schließung, gesetzt bis 60 Minuten vor Ladenschluss, sonst null. Die Website zeigt den Hinweis nur bei Werten bis 30. |
opening_hours | String | eine Zeile | Öffnungszeiten als Text in einer Zeile, Zeiträume durch Komma getrennt. |
raw_openingtimes_json | String | JSON als Text | Rohdaten der Öffnungszeiten. Für die Anzeige ist opening_hours gedacht. |
prices.e5 | Zahl | € je Liter, 3 Nachkommastellen | Preis für Super E5. 0 bedeutet: Die Station bietet die Sorte nicht an. |
prices.e10 | Zahl | € je Liter, 3 Nachkommastellen | Preis für Super E10. 0 bedeutet: Sorte nicht angeboten. |
prices.diesel | Zahl | € je Liter, 3 Nachkommastellen | Preis für Diesel. 0 bedeutet: Sorte nicht angeboten. |
prices.last_change.e5 | String | ISO 8601, kann null sein | Zeitpunkt der letzten Preisänderung für E5, zum Beispiel 2026-10-17T20:14:00Z. |
prices.last_change.e10 | String | ISO 8601, kann null sein | Zeitpunkt der letzten Preisänderung für E10. |
prices.last_change.diesel | String | ISO 8601, kann null sein | Zeitpunkt 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.