Markt-API · öffentliche REST-Schnittstelle

Lesender HTTP/JSON-Zugang zu anonymisierten Markt-Daten — ohne Login, nur per Token in der URL. Stand: 14. Mai 2026.
Für wen? Endkunden mit Markt-Link bekommen über diese API exakt die Daten, die auch das Web-Frontend (preisindex.<domain>) zeigt. Wer per Skript zugreifen will (Python, Excel-PowerQuery, Power BI, Make/Zapier, n8n etc.), nutzt direkt die Endpoints unten.
Inhalt
  1. Authentifizierung
  2. GET /api/markt/angebote
  3. GET /api/markt/branding
  4. Datenschema
  5. Fehlerantworten
  6. Limits & Rate-Limiting
  7. Beispiele (curl, Python, JavaScript)
  8. Alternativen (MCP, Whitelabel)

1. Authentifizierung

Ein Markt-Token (JWT) in der URL als Query-Parameter ?t=…. Token bekommst du:

Der Token ist token-gebunden, nicht user-gebunden — jeder mit dem Token kann lesen. Token können im UI revoked werden. Default-Laufzeit 90 Tage, maximal 365.

Hinweis: Tokens stehen in URL-Logs, Browser-History und Referer-Headern. Bitte nicht öffentlich posten oder in Git-Repos einchecken.

2. GET /api/markt/angebote

GET https://preisindex.<domain>/api/markt/angebote?t=<token>

Liefert alle anonymisierten Angebote als JSON. Anonymisierung:

Response-Shape (200 OK)

{
  "angebote": [
    {
      "id": "heimrich-aufdach-2025-04",
      "konfidenz": 0.92,
      "review_status": "reviewed",
      "datum": "2025-04-12",
      "daten": {
        "id":          "heimrich-aufdach-2025-04",
        "kategorie":   "Aufdachdaemmung_PUR_16",
        "subkategorie": "Schraegdach",
        "produkt": {
          "hersteller":   "Bauder",
          "produktname":  "PIR-PLUS-160",
          "material":     "PUR_PIR",
          "dicke_mm":     160,
          "wls":          0.023
        },
        "menge":   142,
        "einheit": "m2",
        "preis": {
          "ep_netto":                240.00,
          "ep_brutto":               285.60,
          "ep_netto_vergleichbar":   258.00,
          "summe_netto":             34080.00,
          "summe_brutto":            40555.20,
          "aufschlaege": { "geruest": 18.00 }
        },
        "projekt": {
          "region":   "Mittelhessen",
          "bauweise": "EFH"
        },
        "datum":          "2025-04-12",
        "leistungsumfang": {
          "enthalten":      ["demontage_bestand", "entsorgung", "geruest"],
          "nicht_enthalten": ["kran"]
        },
        "nebengewerke_zusammenfassung": [
          { "pos": "Dachfenster", "preis_netto": 950.0, "kommentar": "VELUX GGL" }
        ],
        "tags": ["plausi-auffaellig"]
      }
    },
    ...
  ]
}

Felder im daten-Block

FeldTypBeschreibung
idstringSlug — eindeutig pro Angebot
kategoriestringSlug aus 21 Vergleichs-Kategorien (siehe kategorien_spec.json)
subkategoriestringFreitext-Verfeinerung
produkt.materialstringz.B. EPS, PUR_PIR, Mineralwolle, Holz, Aluminium, ...
produkt.dicke_mmnumberDämmstoff-Dicke in mm
produkt.wlsnumberWärmeleitstufe
produkt.u_wertnumberU-Wert in W/m²K (Fenster/Dachfenster)
produkt.leistung_kwnumberHeizleistung (Wärmepumpe)
produkt.leistung_kwpnumberPV-Modul-Peak
mengenumberMenge
einheitstringm2, Stk, Anlage, kWp, ...
preis.ep_nettonumberEinheitspreis netto
preis.ep_bruttonumberEinheitspreis brutto inkl. 19 % MwSt.
preis.ep_netto_vergleichbarnumberEP netto inkl. kalk. Aufschläge für fehlende Pflicht-Posten
preis.aufschlaegeobjectWas wurde kalk. dazugerechnet (slug → €/Einheit)
projekt.regionstringMittelhessen, Rhein-Main, Westerwald, Taunus, Sonstiges
projekt.bauweisestringEFH, DHH, RH, MFH, WEG, Nichtwohngebaeude, Unbekannt
datumstringAngebotsdatum, Format YYYY-MM-DD
leistungsumfang.enthaltenstring[]Slugs der enthaltenen Nebenleistungen
leistungsumfang.nicht_enthaltenstring[]Slugs der nicht enthaltenen Nebenleistungen
tagsstring[]z.B. plausi-auffaellig, pflicht-posten-fehlen, BAFA-Antrag

Die vollständige Schema-Definition mit allen Enums und optionalen Feldern: data/schema.json.

3. GET /api/markt/branding

GET https://preisindex.<domain>/api/markt/branding?t=<token>

Liefert die Whitelabel-Brand-Informationen des Partners, der den Token erstellt hat (oder leer wenn kein Branding gesetzt).

Response-Shape (200 OK)

{
  "anzeigename":   "Energieberatung Bloemer",
  "logo_url":      "/api/brand-logo/cbloemer.png?v=1747234567",
  "akzent_farbe":  "#2A6F4A",
  "footer_zusatz": "Energieberatung Bloemer · 06195/12345",
  "eigene_domain": "marktpreise.bloemer.de"
}

Nützlich wenn du Markt-Daten in eine eigene UI einbettest und das Partner-Branding mit anzeigen willst.

4. Datenschema

Alle 21 Vergleichs-Kategorien mit Einheit, Pflicht-Posten und Plausi-Ranges:

GET https://preisindex.<domain>/api/spec

Diese Spec ist öffentlich, kein Token nötig — sie ist rein konfigurativ (keine Nutzerdaten). Antwort-Beispiel:

{
  "labels": {
    "WDVS_EPS_16":            "Außendämmung · EPS · 16 cm",
    "Waermepumpe_LuftWasser": "Wärmepumpe · Luft-Wasser · EFH",
    ...
  },
  "specs": {
    "WDVS_EPS_16": {
      "einheit":           "m2",
      "match": {
        "material_in":       ["EPS"],
        "dicke_mm_min":      140,
        "schluesselwoerter": ["wdvs", "wärmedämm-verbundsystem", ...]
      },
      "pflicht_enthalten": ["putz_anschlussarbeiten"],
      "darf_fehlen_mit_aufschlag": {
        "geruest":           12.0,
        "demontage_bestand": 6.0,
        "entsorgung":        4.0
      },
      "range_min": 80.0,
      "range_max": 220.0
    },
    ...
  }
}

5. Fehlerantworten

StatusBedeutungAntwort-Body
401Token ungültig, abgelaufen oder revoked{"detail":"…"}
422?t=-Parameter fehltPydantic-Validation-Error
429Rate-Limit überschritten{"detail":"Zu viele Anfragen. Bitte in ~Xs erneut versuchen."}
5xxServer-Fehler — Marco kontaktierengenerisch

6. Limits & Rate-Limiting

7. Beispiele

curl

TOKEN="eyJhbGciOiJIUzI1NiIs..."   # dein Markt-Token

# Alle Angebote holen, nach Wärmepumpen filtern
curl -s "https://preisindex.<domain>/api/markt/angebote?t=$TOKEN" \
  | jq '.angebote[] | select(.daten.kategorie == "Waermepumpe_LuftWasser")
        | {datum: .daten.datum, kw: .daten.produkt.leistung_kw,
           ep: .daten.preis.ep_brutto}'

# Spec separat holen (kein Token nötig)
curl -s "https://preisindex.<domain>/api/spec" | jq '.labels'

Python

import requests

TOKEN = "eyJhbGciOiJIUzI1NiIs..."
BASE = "https://preisindex.<domain>"

# Alle Angebote
r = requests.get(f"{BASE}/api/markt/angebote", params={"t": TOKEN}, timeout=15)
r.raise_for_status()
angebote = r.json()["angebote"]

# Median-Preis pro Kategorie ausrechnen
from statistics import median
from collections import defaultdict

preise = defaultdict(list)
for a in angebote:
    d = a["daten"]
    p = d["preis"].get("ep_netto_vergleichbar") or d["preis"].get("ep_netto")
    if p:
        preise[d["kategorie"]].append(p * 1.19)   # brutto

for kat, vals in preise.items():
    print(f"{kat:30s} n={len(vals):3d}  median={median(vals):.2f} €")

JavaScript / Node

const TOKEN = "eyJhbGciOiJIUzI1NiIs...";
const BASE  = "https://preisindex.";

const r = await fetch(`${BASE}/api/markt/angebote?t=${TOKEN}`);
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const { angebote } = await r.json();

const wp = angebote.filter(a => a.daten.kategorie === "Waermepumpe_LuftWasser");
console.log(`${wp.length} Wärmepumpen-Angebote`);

Excel / Power Query

let
    Quelle = Json.Document(Web.Contents(
        "https://preisindex.<domain>/api/markt/angebote?t=DEIN_TOKEN")),
    angebote = Quelle[angebote],
    tbl = Table.FromList(angebote, Splitter.SplitByNothing()),
    daten = Table.ExpandRecordColumn(tbl, "Column1", {"daten"}, {"daten"}),
    flach = Table.ExpandRecordColumn(daten, "daten",
        {"kategorie","datum","preis","produkt","projekt"},
        {"kategorie","datum","preis","produkt","projekt"})
in
    flach

8. Alternativen

MCP-JSON-RPC-Endpoint

Komfortablere Tools mit Filtern und Aggregation: mcp-preisindex.<domain>/mcp. Bearer-Token-Auth (im Partner-UI generieren), JSON-RPC 2.0. Tools wie markt_statistik, markt_angebote_listen, angebot_bewerten liefern bereits aggregierte/gefilterte Daten.

Beispiel-Aufruf:

curl -X POST https://mcp-preisindex.<domain>/mcp \
  -H "Authorization: Bearer sp_partner_..." \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "markt_statistik",
      "arguments": { "kategorie": "Waermepumpe_LuftWasser" }
    }
  }'

Whitelabel-Custom-Domain (für Partner)

Wenn du als Partner eine eigene Domain hast (z.B. marktpreise.deinedomain.de), funktioniert die Markt-API unter deiner Domain genauso — einfach den Host austauschen:

curl "https://marktpreise.deinedomain.de/api/markt/angebote?t=$TOKEN"