{"openapi":"3.1.0","info":{"title":"Immolibero Immobiliensuche","version":"1","termsOfService":"https://www.immolibero.com/agb","description":"Öffentliche Suche von Immolibero: veröffentlichte Miet-, Pacht- und Kaufimmobilien in Deutschland. Nur lesend, ohne Anmeldung, Abrufgrenze 120 Anfragen je Minute und Absender-Adresse. Unbekannte oder unpassende Filter werden übergangen und in meta_extras.ignored_filters mit Grund gemeldet. FÜR KI-KLIENTEN GIBT ES /v1/ai/listings UND /v1/ai/listings/{listing}: dieselbe Suche mit denselben Filtern, aber eine schlanke Antwort - ohne leere Felder, mit Objektart, Zustand und Heizung als Namen statt als Schlüssel und mit den Kosten neben dem Preis. Wer Antworten für Menschen formuliert, nimmt diese beiden. Für Kartenansichten gibt es daneben GET /api/v1/listing-map, der alle Treffer eines Gebiets als Kartenpunkte liefert; diese Beschreibung deckt ihn nicht ab. Dieselbe Abrufgrenze gilt für unseren MCP-Server unter POST /mcp."},"servers":[{"url":"https://www.immolibero.com/api"}],"paths":{"/v1/listings":{"get":{"summary":"Inserate suchen","description":"Veröffentlichte Inserate, sortiert nach der internen Rangfolge. Ohne Kategorie wird in allen gesucht.","parameters":[{"name":"q","in":"query","required":false,"description":"Ort (Stadtname) oder Freitext (Titel, PLZ). Steht neben einem Ort mehr, etwa \"Gera 3 Zimmer bis 800 €\", wird im Ort gesucht, und Zimmer (ohne Wort: ab), Preis (ohne Wort: bis), Fläche (ohne Wort: ab) und Objektart werden als Filter übernommen, wenn der passende Parameter nicht gesetzt ist (ein gesendetes price_kind=cold zählt als gesetzt) und die Angabe zur Kategorie passt. Was übernommen wurde, meldet meta_extras.ignored_filters (reason adjusted, applied_filters) bei GET /v1/listings und /v1/ai/listings; GET /v1/listing-map filtert ebenso, meldet es aber nicht.","schema":{"type":"string"}},{"name":"category","in":"query","required":false,"description":"Kategorie; ohne Angabe wird in allen gesucht. Sie legt Vermarktungsart und Objektart zugleich fest und sticht marketing_type und listing_type aus. Die Namen der Ortsseiten-Adressen (etwa wohnung-mieten) gelten hier nicht. Was die Werte bedeuten: mietwohnungen = Wohnung mieten; eigentumswohnungen = Wohnung kaufen; haeuser-mieten = Haus mieten; haeuser-kaufen = Haus kaufen; garage-carport-stellplatz-mieten = Garage, Carport oder Stellplatz mieten; garage-carport-stellplatz-kaufen = Garage, Carport oder Stellplatz kaufen; grundstuecke-bau-wald-garten-pachten = Grundstück pachten; grundstuecke-bau-wald-garten-kaufen = Grundstück kaufen; gewerbe-mieten = Gewerbefläche mieten; gewerbe-kaufen = Gewerbefläche kaufen.","schema":{"type":"string","enum":["mietwohnungen","eigentumswohnungen","haeuser-mieten","haeuser-kaufen","garage-carport-stellplatz-mieten","garage-carport-stellplatz-kaufen","grundstuecke-bau-wald-garten-pachten","grundstuecke-bau-wald-garten-kaufen","gewerbe-mieten","gewerbe-kaufen"]}},{"name":"marketing_type","in":"query","required":false,"description":"Miete oder Kauf. Wirkt nur ohne category.","schema":{"type":"string","enum":["rent","buy"]}},{"name":"listing_type","in":"query","required":false,"description":"Objektart-Gruppe (apartment, house, land, parking, commercial) oder eine einzelne Objektart. Inserate ohne Objektart bleiben enthalten. Wirkt nur ohne category. Mehrere Haustypen zugleich waehlt house_type.","schema":{"type":"string","enum":["apartment","house","land","parking","commercial","apartment","maisonette","penthouse","erdgeschosswohnung","dachgeschosswohnung","etagenwohnung","loft","souterrainwohnung","house","einfamilienhaus","doppelhaushaelfte","reihenhaus","reiheneckhaus","mehrfamilienhaus","ferienhaus","bauernhaus","villa","burg_schloss","besondere_immobilie","sonstige","bungalow","land","aussenstellplatz","carport","doppelgarage","einzelgarage","stellplatz","garage","duplex","tiefgaragenstellplatz","motorradstellplatz","commercial_office","commercial_retail","commercial_hall","commercial_storage","commercial_gastro","commercial_lot","commercial"]}},{"name":"city_id","in":"query","required":false,"description":"Stadt, eindeutig ueber ihre Kennung. Anders als q wird dabei nicht im Text gesucht: Ein Inserat, das den Ortsnamen nur im Titel traegt, ist kein Treffer. Hat Vorrang vor postal und q, steht aber hinter district_id und einem Punkt aus lat und lon. Eine Kennung, die es nicht gibt, filtert nicht und wird in meta_extras.ignored_filters gemeldet.","schema":{"type":"integer"}},{"name":"district_id","in":"query","required":false,"description":"Stadtteil aus GET /v1/search-suggestions: das ganze filters-Objekt uebernehmen. Hat Vorrang vor postal.","schema":{"type":"integer"}},{"name":"postal","in":"query","required":false,"description":"Fuenfstellige Postleitzahl; hat Vorrang vor q.","schema":{"type":"string"}},{"name":"lat","in":"query","required":false,"description":"Breitengrad eines Suchpunkts, aus dem filters-Objekt eines Strassen-Vorschlags. Begrenzt die Suche nur zusammen mit radius.","schema":{"type":"number"}},{"name":"lon","in":"query","required":false,"description":"Laengengrad eines Suchpunkts, gehoert zu lat.","schema":{"type":"number"}},{"name":"near","in":"query","required":false,"description":"Anzeigename des Suchpunkts; filtert nichts. Steht an /v1/listings in meta_extras.headline und an beiden Adressen im Satz von meta_extras.empty_state.next_steps.text, dort nur bei einer Umkreissuche um den Punkt aus lat, lon und radius. An /v1/ai/listings gibt es keine Ueberschrift.","schema":{"type":"string"}},{"name":"radius","in":"query","required":false,"description":"Umkreis in km (1 bis 50), um einen Stadtnamen in q oder um den Punkt aus lat und lon. Bei Postleitzahl oder Stadtteil ohne Wirkung. Gemessen wird am oeffentlichen Kartenpunkt eines Inserats, nicht an seiner Adresse. Zusammen mit lat und lon ist ein Umkreis ausserhalb von 1 bis 50 ein Fehler (422); sonst wird er uebergangen und in meta_extras.ignored_filters gemeldet.","schema":{"type":"integer"}},{"name":"bbox","in":"query","required":false,"description":"Kartenausschnitt \"minLat,minLng,maxLat,maxLng\".","schema":{"type":"string"}},{"name":"price_max","in":"query","required":false,"description":"Hoechstpreis in Euro (Miete oder Kaufpreis); gilt nur ohne rent_to.","schema":{"type":"integer"}},{"name":"rent_from","in":"query","required":false,"description":"Preis ab in Euro (Miete oder Kaufpreis).","schema":{"type":"integer"}},{"name":"rent_to","in":"query","required":false,"description":"Preis bis in Euro (Miete oder Kaufpreis); hat Vorrang vor price_max.","schema":{"type":"integer"}},{"name":"price_kind","in":"query","required":false,"description":"Bezug der Preisgrenzen bei Miete: cold ist die Kaltmiete, warm die Kaltmiete plus Nebenkosten plus Heizkosten; ein Inserat ohne Heizkosten-Angabe erfüllt eine Grenze auf die Warmmiete nicht.","schema":{"type":"string","enum":["cold","warm"]}},{"name":"living_area_from","in":"query","required":false,"description":"Wohn- oder Gewerbeflaeche ab in m².","schema":{"type":"integer"}},{"name":"living_area_to","in":"query","required":false,"description":"Wohn- oder Gewerbeflaeche bis in m².","schema":{"type":"integer"}},{"name":"plot_area_from","in":"query","required":false,"description":"Grundstuecksflaeche ab in m².","schema":{"type":"integer"}},{"name":"plot_area_to","in":"query","required":false,"description":"Grundstuecksflaeche bis in m².","schema":{"type":"integer"}},{"name":"area_min","in":"query","required":false,"description":"Mindest-Wohn- oder Gewerbeflaeche in m² (keine Grundstuecksflaeche); Kurzform von living_area_from. Beide gesetzt, gilt die strengere der zwei Angaben, und die lockere wird in meta_extras.ignored_filters mit reason superseded gemeldet.","schema":{"type":"integer"}},{"name":"rooms","in":"query","required":false,"description":"Mindestanzahl Zimmer; Kurzform von rooms_from. Neben rooms_from ohne Wirkung (gemeldet mit reason superseded); neben rooms_to allein faellt die Untergrenze ganz weg (gemeldet mit reason not_applicable). Beides steht in meta_extras.ignored_filters.","schema":{"type":"number"}},{"name":"rooms_from","in":"query","required":false,"description":"Zimmer ab.","schema":{"type":"number"}},{"name":"rooms_to","in":"query","required":false,"description":"Zimmer bis.","schema":{"type":"number"}},{"name":"house_type","in":"query","required":false,"description":"Haustyp, nur zusammen mit category haeuser-mieten oder haeuser-kaufen (sonst not_applicable); mehrere mit Komma getrennt. Werte: einfamilienhaus, doppelhaushaelfte, reihenhaus, mehrfamilienhaus, bungalow, villa, bauernhaus, ferienhaus, sonstige. reihenhaus umfasst Mittel- und Eckhaus (listing_type=reihenhaus nur das Mittelhaus), sonstige auch Burg, Schloss und besondere Immobilie. Haeuser ohne Angabe zum Haustyp sind immer dabei; alle Werte zusammen filtern nicht. Mehrfamilienhaeuser tragen Wohnungen statt Zimmern: Der Zimmerfilter einer Haussuche laesst sie durch, und mit mehrfamilienhaus allein wirkt er nicht.","schema":{"type":"string"}},{"name":"floor_from","in":"query","required":false,"description":"Etage ab.","schema":{"type":"integer"}},{"name":"floor_to","in":"query","required":false,"description":"Etage bis.","schema":{"type":"integer"}},{"name":"year_built_from","in":"query","required":false,"description":"Baujahr ab, vierstellig.","schema":{"type":"integer"}},{"name":"year_built_to","in":"query","required":false,"description":"Baujahr bis, vierstellig.","schema":{"type":"integer"}},{"name":"energy_class","in":"query","required":false,"description":"Mindestens diese Energieeffizienzklasse; bessere Klassen sind eingeschlossen. Inserate ohne Angabe fallen heraus.","schema":{"type":"string","enum":["A+","A","B","C","D","E","F","G","H"]}},{"name":"heating_type","in":"query","required":false,"description":"Heizungsart.","schema":{"type":"string","enum":["zentralheizung","fussbodenheizung","etagenheizung","fernwaerme","waermepumpe"]}},{"name":"furnished","in":"query","required":false,"description":"true nur moebliert, false nur unmoebliert. Inserate ohne Angabe fallen in beiden Faellen heraus.","schema":{"type":"boolean"}},{"name":"shared_living","in":"query","required":false,"description":"true nur wo eine Wohngemeinschaft erlaubt ist, false nur wo nicht. Inserate ohne Angabe fallen in beiden Faellen heraus.","schema":{"type":"boolean"}},{"name":"balcony_terrace","in":"query","required":false,"description":"Nur mit Balkon oder Terrasse. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"garden_use","in":"query","required":false,"description":"Nur mit Gartennutzung. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"bathtub","in":"query","required":false,"description":"Nur mit Badewanne. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"parking","in":"query","required":false,"description":"Nur mit Stellplatz oder Garage. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"elevator","in":"query","required":false,"description":"Nur mit Aufzug. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"basement","in":"query","required":false,"description":"Nur mit Keller. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"guest_wc","in":"query","required":false,"description":"Nur mit Gäste-WC oder zweitem Bad. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"ground_floor","in":"query","required":false,"description":"Nur Erdgeschoss. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"top_floor","in":"query","required":false,"description":"Nur Dachgeschoss. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"maisonette","in":"query","required":false,"description":"Nur Maisonette. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"pet_policy","in":"query","required":false,"description":"yes erlaubt, no keine Haustiere, by_agreement nach Vereinbarung. yes schliesst \"nach Vereinbarung\" nicht ein.","schema":{"type":"string","enum":["yes","no","by_agreement"]}},{"name":"sort","in":"query","required":false,"description":"Sortierung; relevance ist der Standard und sortiert nach der internen Rangfolge, nicht nach Alter.","schema":{"type":"string","enum":["relevance","price_asc","price_desc","newest"]}},{"name":"page","in":"query","required":false,"description":"Seite ab 1. Jenseits von meta.last_page kommt die letzte Seite; das Ende erkennt man an meta.last_page.","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Treffer je Seite, 1 bis 10. Ohne Angabe sind es 12.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Antwort","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"}}}},"422":{"description":"Ein Punkt aus lat und lon ist halb, unmöglich oder sein Umkreis liegt außerhalb von 1 bis 50 km. Alle übrigen unpassenden Angaben werden übergangen und in meta_extras.ignored_filters gemeldet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Zu viele Anfragen; der Kopfzeile Retry-After ist zu entnehmen, ab wann wieder.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/listings/{listing}":{"get":{"summary":"Ein Inserat","description":"Ein einzelnes veröffentlichtes Inserat. Abgeschaltete Inserate antworten mit 404; nennt die Website einen Grund, steht er in error.message und error.details.reason ist deactivated.","parameters":[{"name":"listing","in":"path","required":true,"description":"Slug oder numerische Kennung des Inserats, beides aus dem Pfad von expose_url (ein angehängtes ?via zählt nicht dazu) bzw. id der Trefferliste.","schema":{"type":"string"}}],"responses":{"200":{"description":"Antwort","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Listing"}},"required":["data"],"additionalProperties":false}}}},"404":{"description":"Das Inserat gibt es nicht oder es ist nicht mehr veröffentlicht.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Zu viele Anfragen; der Kopfzeile Retry-After ist zu entnehmen, ab wann wieder.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/ai/listings":{"get":{"summary":"Inserate suchen, schlanke Antwort","description":"DIESELBE SUCHE wie /v1/listings - dieselben Filter, dieselbe Reihenfolge-Logik, dieselben Grenzen - aber eine Antwort, die auf das Formulieren von Sätzen zugeschnitten ist. Felder ohne Wert fehlen ganz, statt null zu tragen. Objektart, Zustand, Heizung und Haustierregelung stehen als Name UND als Schlüssel: der Name für den Satz, der Schlüssel für die Anschlussfrage. Die Kosten neben dem Preis (Nebenkosten, Heizkosten, Kaution, Hausgeld, Käuferprovision) stehen dabei, weil ein Preis ohne sie zu falschen Aussagen führt.","parameters":[{"name":"q","in":"query","required":false,"description":"Ort (Stadtname) oder Freitext (Titel, PLZ). Steht neben einem Ort mehr, etwa \"Gera 3 Zimmer bis 800 €\", wird im Ort gesucht, und Zimmer (ohne Wort: ab), Preis (ohne Wort: bis), Fläche (ohne Wort: ab) und Objektart werden als Filter übernommen, wenn der passende Parameter nicht gesetzt ist (ein gesendetes price_kind=cold zählt als gesetzt) und die Angabe zur Kategorie passt. Was übernommen wurde, meldet meta_extras.ignored_filters (reason adjusted, applied_filters) bei GET /v1/listings und /v1/ai/listings; GET /v1/listing-map filtert ebenso, meldet es aber nicht.","schema":{"type":"string"}},{"name":"category","in":"query","required":false,"description":"Kategorie; ohne Angabe wird in allen gesucht. Sie legt Vermarktungsart und Objektart zugleich fest und sticht marketing_type und listing_type aus. Die Namen der Ortsseiten-Adressen (etwa wohnung-mieten) gelten hier nicht. Was die Werte bedeuten: mietwohnungen = Wohnung mieten; eigentumswohnungen = Wohnung kaufen; haeuser-mieten = Haus mieten; haeuser-kaufen = Haus kaufen; garage-carport-stellplatz-mieten = Garage, Carport oder Stellplatz mieten; garage-carport-stellplatz-kaufen = Garage, Carport oder Stellplatz kaufen; grundstuecke-bau-wald-garten-pachten = Grundstück pachten; grundstuecke-bau-wald-garten-kaufen = Grundstück kaufen; gewerbe-mieten = Gewerbefläche mieten; gewerbe-kaufen = Gewerbefläche kaufen.","schema":{"type":"string","enum":["mietwohnungen","eigentumswohnungen","haeuser-mieten","haeuser-kaufen","garage-carport-stellplatz-mieten","garage-carport-stellplatz-kaufen","grundstuecke-bau-wald-garten-pachten","grundstuecke-bau-wald-garten-kaufen","gewerbe-mieten","gewerbe-kaufen"]}},{"name":"marketing_type","in":"query","required":false,"description":"Miete oder Kauf. Wirkt nur ohne category.","schema":{"type":"string","enum":["rent","buy"]}},{"name":"listing_type","in":"query","required":false,"description":"Objektart-Gruppe (apartment, house, land, parking, commercial) oder eine einzelne Objektart. Inserate ohne Objektart bleiben enthalten. Wirkt nur ohne category. Mehrere Haustypen zugleich waehlt house_type.","schema":{"type":"string","enum":["apartment","house","land","parking","commercial","apartment","maisonette","penthouse","erdgeschosswohnung","dachgeschosswohnung","etagenwohnung","loft","souterrainwohnung","house","einfamilienhaus","doppelhaushaelfte","reihenhaus","reiheneckhaus","mehrfamilienhaus","ferienhaus","bauernhaus","villa","burg_schloss","besondere_immobilie","sonstige","bungalow","land","aussenstellplatz","carport","doppelgarage","einzelgarage","stellplatz","garage","duplex","tiefgaragenstellplatz","motorradstellplatz","commercial_office","commercial_retail","commercial_hall","commercial_storage","commercial_gastro","commercial_lot","commercial"]}},{"name":"city_id","in":"query","required":false,"description":"Stadt, eindeutig ueber ihre Kennung. Anders als q wird dabei nicht im Text gesucht: Ein Inserat, das den Ortsnamen nur im Titel traegt, ist kein Treffer. Hat Vorrang vor postal und q, steht aber hinter district_id und einem Punkt aus lat und lon. Eine Kennung, die es nicht gibt, filtert nicht und wird in meta_extras.ignored_filters gemeldet.","schema":{"type":"integer"}},{"name":"district_id","in":"query","required":false,"description":"Stadtteil aus GET /v1/search-suggestions: das ganze filters-Objekt uebernehmen. Hat Vorrang vor postal.","schema":{"type":"integer"}},{"name":"postal","in":"query","required":false,"description":"Fuenfstellige Postleitzahl; hat Vorrang vor q.","schema":{"type":"string"}},{"name":"lat","in":"query","required":false,"description":"Breitengrad eines Suchpunkts, aus dem filters-Objekt eines Strassen-Vorschlags. Begrenzt die Suche nur zusammen mit radius.","schema":{"type":"number"}},{"name":"lon","in":"query","required":false,"description":"Laengengrad eines Suchpunkts, gehoert zu lat.","schema":{"type":"number"}},{"name":"near","in":"query","required":false,"description":"Anzeigename des Suchpunkts; filtert nichts. Steht an /v1/listings in meta_extras.headline und an beiden Adressen im Satz von meta_extras.empty_state.next_steps.text, dort nur bei einer Umkreissuche um den Punkt aus lat, lon und radius. An /v1/ai/listings gibt es keine Ueberschrift.","schema":{"type":"string"}},{"name":"radius","in":"query","required":false,"description":"Umkreis in km (1 bis 50), um einen Stadtnamen in q oder um den Punkt aus lat und lon. Bei Postleitzahl oder Stadtteil ohne Wirkung. Gemessen wird am oeffentlichen Kartenpunkt eines Inserats, nicht an seiner Adresse. Zusammen mit lat und lon ist ein Umkreis ausserhalb von 1 bis 50 ein Fehler (422); sonst wird er uebergangen und in meta_extras.ignored_filters gemeldet.","schema":{"type":"integer"}},{"name":"bbox","in":"query","required":false,"description":"Kartenausschnitt \"minLat,minLng,maxLat,maxLng\".","schema":{"type":"string"}},{"name":"price_max","in":"query","required":false,"description":"Hoechstpreis in Euro (Miete oder Kaufpreis); gilt nur ohne rent_to.","schema":{"type":"integer"}},{"name":"rent_from","in":"query","required":false,"description":"Preis ab in Euro (Miete oder Kaufpreis).","schema":{"type":"integer"}},{"name":"rent_to","in":"query","required":false,"description":"Preis bis in Euro (Miete oder Kaufpreis); hat Vorrang vor price_max.","schema":{"type":"integer"}},{"name":"price_kind","in":"query","required":false,"description":"Bezug der Preisgrenzen bei Miete: cold ist die Kaltmiete, warm die Kaltmiete plus Nebenkosten plus Heizkosten; ein Inserat ohne Heizkosten-Angabe erfüllt eine Grenze auf die Warmmiete nicht.","schema":{"type":"string","enum":["cold","warm"]}},{"name":"living_area_from","in":"query","required":false,"description":"Wohn- oder Gewerbeflaeche ab in m².","schema":{"type":"integer"}},{"name":"living_area_to","in":"query","required":false,"description":"Wohn- oder Gewerbeflaeche bis in m².","schema":{"type":"integer"}},{"name":"plot_area_from","in":"query","required":false,"description":"Grundstuecksflaeche ab in m².","schema":{"type":"integer"}},{"name":"plot_area_to","in":"query","required":false,"description":"Grundstuecksflaeche bis in m².","schema":{"type":"integer"}},{"name":"area_min","in":"query","required":false,"description":"Mindest-Wohn- oder Gewerbeflaeche in m² (keine Grundstuecksflaeche); Kurzform von living_area_from. Beide gesetzt, gilt die strengere der zwei Angaben, und die lockere wird in meta_extras.ignored_filters mit reason superseded gemeldet.","schema":{"type":"integer"}},{"name":"rooms","in":"query","required":false,"description":"Mindestanzahl Zimmer; Kurzform von rooms_from. Neben rooms_from ohne Wirkung (gemeldet mit reason superseded); neben rooms_to allein faellt die Untergrenze ganz weg (gemeldet mit reason not_applicable). Beides steht in meta_extras.ignored_filters.","schema":{"type":"number"}},{"name":"rooms_from","in":"query","required":false,"description":"Zimmer ab.","schema":{"type":"number"}},{"name":"rooms_to","in":"query","required":false,"description":"Zimmer bis.","schema":{"type":"number"}},{"name":"house_type","in":"query","required":false,"description":"Haustyp, nur zusammen mit category haeuser-mieten oder haeuser-kaufen (sonst not_applicable); mehrere mit Komma getrennt. Werte: einfamilienhaus, doppelhaushaelfte, reihenhaus, mehrfamilienhaus, bungalow, villa, bauernhaus, ferienhaus, sonstige. reihenhaus umfasst Mittel- und Eckhaus (listing_type=reihenhaus nur das Mittelhaus), sonstige auch Burg, Schloss und besondere Immobilie. Haeuser ohne Angabe zum Haustyp sind immer dabei; alle Werte zusammen filtern nicht. Mehrfamilienhaeuser tragen Wohnungen statt Zimmern: Der Zimmerfilter einer Haussuche laesst sie durch, und mit mehrfamilienhaus allein wirkt er nicht.","schema":{"type":"string"}},{"name":"floor_from","in":"query","required":false,"description":"Etage ab.","schema":{"type":"integer"}},{"name":"floor_to","in":"query","required":false,"description":"Etage bis.","schema":{"type":"integer"}},{"name":"year_built_from","in":"query","required":false,"description":"Baujahr ab, vierstellig.","schema":{"type":"integer"}},{"name":"year_built_to","in":"query","required":false,"description":"Baujahr bis, vierstellig.","schema":{"type":"integer"}},{"name":"energy_class","in":"query","required":false,"description":"Mindestens diese Energieeffizienzklasse; bessere Klassen sind eingeschlossen. Inserate ohne Angabe fallen heraus.","schema":{"type":"string","enum":["A+","A","B","C","D","E","F","G","H"]}},{"name":"heating_type","in":"query","required":false,"description":"Heizungsart.","schema":{"type":"string","enum":["zentralheizung","fussbodenheizung","etagenheizung","fernwaerme","waermepumpe"]}},{"name":"furnished","in":"query","required":false,"description":"true nur moebliert, false nur unmoebliert. Inserate ohne Angabe fallen in beiden Faellen heraus.","schema":{"type":"boolean"}},{"name":"shared_living","in":"query","required":false,"description":"true nur wo eine Wohngemeinschaft erlaubt ist, false nur wo nicht. Inserate ohne Angabe fallen in beiden Faellen heraus.","schema":{"type":"boolean"}},{"name":"balcony_terrace","in":"query","required":false,"description":"Nur mit Balkon oder Terrasse. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"garden_use","in":"query","required":false,"description":"Nur mit Gartennutzung. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"bathtub","in":"query","required":false,"description":"Nur mit Badewanne. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"parking","in":"query","required":false,"description":"Nur mit Stellplatz oder Garage. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"elevator","in":"query","required":false,"description":"Nur mit Aufzug. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"basement","in":"query","required":false,"description":"Nur mit Keller. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"guest_wc","in":"query","required":false,"description":"Nur mit Gäste-WC oder zweitem Bad. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"ground_floor","in":"query","required":false,"description":"Nur Erdgeschoss. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"top_floor","in":"query","required":false,"description":"Nur Dachgeschoss. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"maisonette","in":"query","required":false,"description":"Nur Maisonette. Wirkt nur als Einschränkung: 1 bzw. true filtert, 0 bzw. false filtert nicht.","schema":{"type":"boolean"}},{"name":"pet_policy","in":"query","required":false,"description":"yes erlaubt, no keine Haustiere, by_agreement nach Vereinbarung. yes schliesst \"nach Vereinbarung\" nicht ein.","schema":{"type":"string","enum":["yes","no","by_agreement"]}},{"name":"sort","in":"query","required":false,"description":"Sortierung; relevance ist der Standard und sortiert nach der internen Rangfolge, nicht nach Alter.","schema":{"type":"string","enum":["relevance","price_asc","price_desc","newest"]}},{"name":"page","in":"query","required":false,"description":"Seite ab 1. Jenseits von meta.last_page kommt die letzte Seite; das Ende erkennt man an meta.last_page.","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Treffer je Seite, 1 bis 10. Ohne Angabe sind es 12.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Antwort","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiSearchResponse"}}}},"422":{"description":"Ein Punkt aus lat und lon ist halb, unmöglich oder sein Umkreis liegt außerhalb von 1 bis 50 km. Alle übrigen unpassenden Angaben werden übergangen und in meta_extras.ignored_filters gemeldet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Zu viele Anfragen; der Kopfzeile Retry-After ist zu entnehmen, ab wann wieder. Das Kontingent von 120 Anfragen je Minute gilt für alle hier beschriebenen Endpunkte, diese Beschreibung und den MCP-Server unter POST /mcp zusammen; GET /api/v1/listing-map hat ein eigenes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/ai/listings/{listing}":{"get":{"summary":"Ein Inserat, schlanke Antwort","description":"Wie /v1/listings/{listing}, aber schlank - und mit den Pflichtangaben nach § 87 GEG als benannte Felder statt als Textzeile. Abgeschaltete Inserate antworten mit 404; nennt die Website einen Grund, steht er in error.message und error.details.reason ist deactivated.","parameters":[{"name":"listing","in":"path","required":true,"description":"Slug des Inserats, aus dem Pfad von expose_url (ein angehängtes ?via zählt nicht dazu). Eine numerische Kennung wird ebenfalls aufgelöst.","schema":{"type":"string"}}],"responses":{"200":{"description":"Antwort","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AiListing"}},"required":["data"],"additionalProperties":false}}}},"404":{"description":"Das Inserat gibt es nicht oder es ist nicht mehr veröffentlicht.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Zu viele Anfragen; der Kopfzeile Retry-After ist zu entnehmen, ab wann wieder.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/search-suggestions":{"get":{"summary":"Orte vorschlagen","description":"Städte, Stadtteile, Postleitzahlen, Regionen und Straßen zu einer Eingabe. Das filters-Objekt eines Vorschlags wird unverändert an GET /v1/listings weitergereicht.","parameters":[{"name":"q","in":"query","required":false,"description":"Eingabe des Nutzers, ab zwei Zeichen.","schema":{"type":"string"}}],"responses":{"200":{"description":"Antwort","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SearchSuggestion"}}},"required":["data"],"additionalProperties":false}}}}}}}},"components":{"schemas":{"PriceSummary":{"type":"object","properties":{"amount_cents":{"type":["integer","null"],"description":"Betrag in Cent; null, wenn kein Betrag gilt (siehe on_request)."},"currency":{"type":"string"},"on_request":{"type":"boolean","description":"Preis auf Anfrage: Es gilt kein Betrag - der Anbieter nennt ihn nicht, oder es ist kein Preis über 0 hinterlegt. Der Betrag steht dann nicht in der Antwort."},"unit":{"type":["string","null"],"enum":["month","sqm",null],"description":"Worauf sich der Betrag bezieht: month je Monat, sqm je m² im Monat, null beim Kauf."},"label":{"type":"string","description":"Das Wort zum Betrag, wie es auf der Seite steht: Kaufpreis, Kaltmiete, Miete oder Pacht. Bei einer Miete je m² steht \"/m²\" dahinter."},"cold_rent_unit":{"type":["string","null"],"description":"Die gespeicherte Mieteinheit. Wer die Einheit braucht, liest unit."},"price_word":{"type":"string","description":"Dasselbe Wort ohne die Einheit, und beim Kauf leer: Dort steht der Betrag für sich."}},"required":["amount_cents","currency","on_request","unit","label","cold_rent_unit","price_word"],"additionalProperties":false},"PriceDetail":{"type":"object","properties":{"amount_cents":{"type":["integer","null"],"description":"Betrag in Cent; null, wenn kein Betrag gilt (siehe on_request)."},"currency":{"type":"string"},"on_request":{"type":"boolean","description":"Preis auf Anfrage: Es gilt kein Betrag - der Anbieter nennt ihn nicht, oder es ist kein Preis über 0 hinterlegt. Der Betrag steht dann nicht in der Antwort."},"unit":{"type":["string","null"],"enum":["month","sqm",null],"description":"Worauf sich der Betrag bezieht: month je Monat, sqm je m² im Monat, null beim Kauf."},"label":{"type":"string","description":"Das Wort zum Betrag, wie es auf der Seite steht: Kaufpreis, Kaltmiete, Miete oder Pacht. Bei einer Miete je m² steht \"/m²\" dahinter."},"cold_rent_unit":{"type":["string","null"],"description":"Die gespeicherte Mieteinheit. Wer die Einheit braucht, liest unit."},"price_word":{"type":"string","description":"Dasselbe Wort ohne die Einheit, und beim Kauf leer: Dort steht der Betrag für sich."},"breakdown":{"type":"object","description":"Einzelne Posten in Cent, soweit angegeben: cold_rent_cents, deposit_cents, additional_costs_cents, heating_costs_cents, wohngeld_cents. Bei on_request ist cold_rent_cents null."},"courtage":{"type":["object","null"],"description":"Provision: type ist free, percent oder amount."}},"required":["amount_cents","currency","on_request","unit","label","cold_rent_unit","price_word","breakdown","courtage"],"additionalProperties":false},"ListingSummary":{"type":"object","properties":{"id":{"type":"integer"},"slug":{"type":"string"},"title":{"type":"string"},"marketing_type":{"type":"string","enum":["rent","buy"]},"listing_type_key":{"type":["string","null"],"description":"Objektart, etwa etagenwohnung oder einfamilienhaus."},"property_category":{"type":["string","null"],"description":"Gruppe der Objektart: apartment, house, land, parking oder commercial."},"price":{"$ref":"#/components/schemas/PriceSummary"},"rooms":{"type":["number","null"]},"living_area_sqm":{"type":["number","null"]},"plot_area_sqm":{"type":["number","null"]},"primary_area":{"type":"object","description":"Die Hauptfläche mit der Beschriftung, die zur Objektart passt: bei Gewerbe Gewerbefläche, beim Stellplatz Fläche.","properties":{"key":{"type":"string"},"label":{"type":"string"},"value_sqm":{"type":["number","null"]}},"required":["key","label","value_sqm"],"additionalProperties":false},"address":{"type":"object","properties":{"postal_code":{"type":["string","null"]},"city":{"type":["string","null"]},"district":{"type":["string","null"]},"coordinates":{"type":["object","null"],"description":"lat, lon und is_exact. Bei einem Inserat mit verborgener Adresse ist es der Mittelpunkt des Stadtteils oder der Stadt, is_exact ist dann false."},"location_precision":{"type":["string","null"]}},"required":["postal_code","city","district","coordinates","location_precision"],"additionalProperties":false},"cover_image_url":{"type":["string","null"],"description":"Titelbild, Adresse relativ zur Wurzel."},"cover_image_absolute_url":{"type":["string","null"],"description":"Dasselbe Titelbild mit vollem Gastgeber."},"published_at":{"type":["string","null"]},"expose_url":{"type":"string","description":"Die Seite zum Inserat; diese Adresse gehört unverändert in eine Antwort, die auf das Inserat verweist - nicht kürzen, nicht normalisieren. Ohne erkennbare Mobile-App trägt sie den Parameter via=api."},"warm_rent":{"type":["object","null"],"description":"Kaltmiete plus Nebenkosten plus Heizkosten, nur bei Wohnungen und Häusern zur Monatsmiete mit sichtbarem Preis und mit Heizkosten-Angabe; sonst null.","properties":{"amount_cents":{"type":"integer","description":"Betrag in Cent."},"currency":{"type":"string"}},"required":["amount_cents","currency"],"additionalProperties":false},"is_favorited":{"type":["boolean","null"],"description":"Nur mit Anmeldung, sonst null."},"provider":{"type":"object","description":"verification_status und is_verified des Anbieters. verification_status trägt verified bei bestätigter Gewerbeprüfung, not_checked bei einem privaten Anbieter und null, wo das Portal nichts aussagt. Ein laufender, offener oder abgelehnter Prüfvorgang ist nicht ablesbar. is_verified false heißt nicht, dass eine Prüfung negativ ausgegangen ist."},"is_available":{"type":"boolean"}},"required":["id","slug","title","marketing_type","listing_type_key","property_category","price","rooms","living_area_sqm","plot_area_sqm","primary_area","address","cover_image_url","cover_image_absolute_url","published_at","expose_url","warm_rent","is_favorited","provider","is_available"],"additionalProperties":false},"AiSearchResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AiListingSummary"}},"links":{"type":"object","description":"first, last, prev und next als vollständige Adressen; next ist null auf der letzten Seite."},"meta":{"type":"object","description":"current_page, last_page, per_page, total, from, to und path. total zählt höchstens 1000 (die blätterbaren Treffer), die ganze Zahl steht in meta_extras.results_total. Die Seitenknöpfe (meta.links) stehen hier nicht - wohin die nächste Seite geht, sagt links.next."},"meta_extras":{"type":"object","properties":{"results_total":{"type":"integer","description":"Alle Treffer der Suche. Anders als meta.total nicht bei 1000 gekappt."},"ignored_filters":{"type":"array","items":{"$ref":"#/components/schemas/IgnoredFilter"},"description":"Was an der Anfrage nicht gewirkt hat. Eine leere Liste heißt: alles hat gewirkt."},"empty_state":{"$ref":"#/components/schemas/AiEmptyState"}},"required":["ignored_filters"],"additionalProperties":false}},"required":["data","links","meta","meta_extras"],"additionalProperties":false},"AiListingSummary":{"type":"object","description":"Ein Inserat in der Trefferliste. Was hier fehlt, ist nicht angegeben.","properties":{"slug":{"type":"string","description":"Kennung für GET /v1/ai/listings/{listing}."},"title":{"type":"string"},"listing_type":{"$ref":"#/components/schemas/AiBenannt"},"marketing_type":{"type":"object","properties":{"key":{"type":"string","enum":["rent","buy"]},"label":{"type":"string","description":"Miete, Pacht oder Kauf - \"rent\" allein macht aus einer Pacht wieder eine Miete."}},"required":["key","label"],"additionalProperties":false},"price":{"type":"object","description":"Der Hauptpreis. Fehlt amount_cents bei on_request true, ist das die Aussage \"Preis auf Anfrage\".","properties":{"amount_cents":{"type":"integer"},"currency":{"type":"string"},"on_request":{"type":"boolean"},"unit":{"type":"string","enum":["month","sqm"],"description":"month oder sqm (je m² im Monat). Fehlt beim Kauf."},"label":{"type":"string","description":"Die Zeilenbeschriftung: Kaufpreis, Kaltmiete, Miete, Pacht - bei Gewerbe je m² mit Zusatz."},"word":{"type":"string","description":"Dasselbe Wort HINTER einer Zahl (\"700 € Kaltmiete\"). Fehlt beim Kauf: Dort steht der Betrag für sich (\"389.000 €\"), die Zeilenbeschriftung label sagt Kaufpreis."}},"required":["currency","on_request","label"],"additionalProperties":false},"costs":{"type":"object","description":"Was neben dem Hauptpreis anfällt. warm_rent steht NUR da, wenn Neben- oder Heizkosten gepflegt sind. Fehlt es, heißt das nicht \"keine Nebenkosten\", sondern: dazu ist nichts angegeben.","properties":{"additional_costs_cents":{"type":"integer"},"heating_costs_cents":{"type":"integer"},"heating_cost_type":{"$ref":"#/components/schemas/AiBenannt"},"deposit_cents":{"type":"integer","description":"Kaution in Cent."},"wohngeld_cents":{"type":"integer","description":"Hausgeld beim Kauf."},"parking_rent_cents":{"type":"integer","description":"Miete je Stellplatz, in Cent. Sie steckt NICHT in warm_rent: Den Stellplatz mietet man dazu, und nur wer ihn mitmietet, zahlt sie. Steht bei Objekten mit Wohnformular zur Miete, bei denen ein Stellplatz angegeben ist - dazu zählt die Objektart Gewerbe, nicht aber Hallen, Läden und die übrigen Gewerbearten."},"warm_rent":{"type":"object","properties":{"amount_cents":{"type":"integer"},"currency":{"type":"string"}},"required":["amount_cents","currency"],"additionalProperties":false},"buyer_commission":{"type":"string","description":"Käuferprovision, fertig lesbar - etwa \"3,57 %\". Ohne sie ist eine Aussage über den Kaufpreis unvollständig."}},"required":[],"additionalProperties":false},"rooms":{"type":"number"},"area":{"type":"object","description":"Die führende Fläche, je nach Objektart benannt.","properties":{"label":{"type":"string"},"value_sqm":{"type":"number"}},"required":["label","value_sqm"],"additionalProperties":false},"area_secondary":{"type":"array","description":"Weitere Flächen - beim Haus das Grundstück, beim Gewerbe Verkaufs-, Hallen- oder Lagerfläche. Leere Liste heißt: es gibt keine.","items":{"type":"object","properties":{"label":{"type":"string"},"value_sqm":{"type":"number"}},"required":["label","value_sqm"],"additionalProperties":false}},"address":{"type":"object","description":"Die Lage. location_precision sagt, wie genau der Punkt ist - ohne diesen Wert läse man eine Stadtteilmitte als Hausposition.","properties":{"postal_code":{"type":"string"},"city":{"type":"string"},"district":{"type":"string"},"region":{"type":"string","description":"Das Bundesland. Treffer in gleichnamigen Orten lassen sich daran trennen; Ortsnamen kommen in Deutschland mehrfach vor. Einen eigenen Filter dafür gibt es nicht: Ein Bundesland als q sucht im Text, und einen eindeutigen Ort liefert GET /v1/search-suggestions als city_id."},"coordinates":{"type":"object","properties":{"lat":{"type":"number"},"lon":{"type":"number"},"is_exact":{"type":"boolean"}},"required":["lat","lon","is_exact"],"additionalProperties":false},"location_precision":{"type":"string","enum":["exact","district","city"]}},"required":[],"additionalProperties":false},"available_from":{"type":"string","description":"ISO 8601."},"available_immediately":{"type":"boolean"},"published_at":{"type":"string","description":"ISO 8601. Damit lässt sich sagen, wie lange ein Inserat schon online ist."},"cover_image_absolute_url":{"type":"string","description":"Titelbild, vollständige Adresse. Nur echte Anzeigebilder - nie ein Grundriss oder ein Dokument."},"expose_url":{"type":"string","description":"Die Seite für Menschen. Unverändert weitergeben, nicht kürzen, nicht normalisieren."},"parking_dimensions":{"type":"object","description":"Die Maße eines Stellplatzes in Metern, und nur bei Stellplätzen, Garagen und Carports. line ist Länge × Breite als fertige Zeile, wie das Portal sie setzt - auf eine Nachkommastelle gerundet und ohne die Höhe. Wer genau rechnen muss, nimmt die Einzelfelder: Sie führen zwei Nachkommastellen. Die Zeile entsteht nur, wenn Länge und Breite gefüllt sind. Keine Flächenangabe - die steht in area.","properties":{"length_m":{"type":"number"},"width_m":{"type":"number"},"height_m":{"type":"number"},"line":{"type":"string"}},"required":[],"additionalProperties":false},"pet_policy":{"$ref":"#/components/schemas/AiBenannt"},"amenities":{"type":"object","description":"Ausstattung. available nennt, was vorhanden ist; not_available nur das, was der Anbieter AUSDRÜCKLICH verneint hat. Eine leere not_available-Liste heißt NICHT, dass alles andere vorhanden ist. Fehlt das Feld ganz, wurde nach Ausstattung nie gefragt (Grundstück, Stellplatz).","properties":{"available":{"type":"array","items":{"type":"string"}},"not_available":{"type":"array","items":{"type":"string"}}},"required":["available","not_available"],"additionalProperties":false},"provider":{"type":"object","description":"Der Anbieter: sein öffentlicher Name und seine Art. Über den Prüfstand eines Anbieters sagt diese Adresse nichts; wer ihn braucht, nimmt GET /v1/listings bzw. GET /v1/listings/{listing}.","properties":{"display_name":{"type":"string"},"type":{"$ref":"#/components/schemas/AiBenannt"}},"required":["display_name"],"additionalProperties":false}},"required":["slug","title","marketing_type","price","costs","area_secondary","expose_url","provider"],"additionalProperties":false},"AiListing":{"type":"object","description":"Ein einzelnes Inserat, schlank. Dieselben Felder wie in der Trefferliste, dazu Beschreibung, Eckdaten, Zustand, Möblierung, Heizung, Baujahr und die Pflichtangaben nach § 87 GEG.","properties":{"slug":{"type":"string","description":"Kennung für GET /v1/ai/listings/{listing}."},"title":{"type":"string"},"listing_type":{"$ref":"#/components/schemas/AiBenannt"},"marketing_type":{"type":"object","properties":{"key":{"type":"string","enum":["rent","buy"]},"label":{"type":"string","description":"Miete, Pacht oder Kauf - \"rent\" allein macht aus einer Pacht wieder eine Miete."}},"required":["key","label"],"additionalProperties":false},"price":{"type":"object","description":"Der Hauptpreis. Fehlt amount_cents bei on_request true, ist das die Aussage \"Preis auf Anfrage\".","properties":{"amount_cents":{"type":"integer"},"currency":{"type":"string"},"on_request":{"type":"boolean"},"unit":{"type":"string","enum":["month","sqm"],"description":"month oder sqm (je m² im Monat). Fehlt beim Kauf."},"label":{"type":"string","description":"Die Zeilenbeschriftung: Kaufpreis, Kaltmiete, Miete, Pacht - bei Gewerbe je m² mit Zusatz."},"word":{"type":"string","description":"Dasselbe Wort HINTER einer Zahl (\"700 € Kaltmiete\"). Fehlt beim Kauf: Dort steht der Betrag für sich (\"389.000 €\"), die Zeilenbeschriftung label sagt Kaufpreis."}},"required":["currency","on_request","label"],"additionalProperties":false},"costs":{"type":"object","description":"Was neben dem Hauptpreis anfällt. warm_rent steht NUR da, wenn Neben- oder Heizkosten gepflegt sind. Fehlt es, heißt das nicht \"keine Nebenkosten\", sondern: dazu ist nichts angegeben.","properties":{"additional_costs_cents":{"type":"integer"},"heating_costs_cents":{"type":"integer"},"heating_cost_type":{"$ref":"#/components/schemas/AiBenannt"},"deposit_cents":{"type":"integer","description":"Kaution in Cent."},"wohngeld_cents":{"type":"integer","description":"Hausgeld beim Kauf."},"parking_rent_cents":{"type":"integer","description":"Miete je Stellplatz, in Cent. Sie steckt NICHT in warm_rent: Den Stellplatz mietet man dazu, und nur wer ihn mitmietet, zahlt sie. Steht bei Objekten mit Wohnformular zur Miete, bei denen ein Stellplatz angegeben ist - dazu zählt die Objektart Gewerbe, nicht aber Hallen, Läden und die übrigen Gewerbearten."},"warm_rent":{"type":"object","properties":{"amount_cents":{"type":"integer"},"currency":{"type":"string"}},"required":["amount_cents","currency"],"additionalProperties":false},"buyer_commission":{"type":"string","description":"Käuferprovision, fertig lesbar - etwa \"3,57 %\". Ohne sie ist eine Aussage über den Kaufpreis unvollständig."}},"required":[],"additionalProperties":false},"rooms":{"type":"number"},"area":{"type":"object","description":"Die führende Fläche, je nach Objektart benannt.","properties":{"label":{"type":"string"},"value_sqm":{"type":"number"}},"required":["label","value_sqm"],"additionalProperties":false},"area_secondary":{"type":"array","description":"Weitere Flächen - beim Haus das Grundstück, beim Gewerbe Verkaufs-, Hallen- oder Lagerfläche. Leere Liste heißt: es gibt keine.","items":{"type":"object","properties":{"label":{"type":"string"},"value_sqm":{"type":"number"}},"required":["label","value_sqm"],"additionalProperties":false}},"address":{"type":"object","description":"Die Lage. location_precision sagt, wie genau der Punkt ist - ohne diesen Wert läse man eine Stadtteilmitte als Hausposition.","properties":{"postal_code":{"type":"string"},"city":{"type":"string"},"district":{"type":"string"},"region":{"type":"string","description":"Das Bundesland. Treffer in gleichnamigen Orten lassen sich daran trennen; Ortsnamen kommen in Deutschland mehrfach vor. Einen eigenen Filter dafür gibt es nicht: Ein Bundesland als q sucht im Text, und einen eindeutigen Ort liefert GET /v1/search-suggestions als city_id."},"coordinates":{"type":"object","properties":{"lat":{"type":"number"},"lon":{"type":"number"},"is_exact":{"type":"boolean"}},"required":["lat","lon","is_exact"],"additionalProperties":false},"location_precision":{"type":"string","enum":["exact","district","city"]}},"required":[],"additionalProperties":false},"available_from":{"type":"string","description":"ISO 8601."},"available_immediately":{"type":"boolean"},"published_at":{"type":"string","description":"ISO 8601. Damit lässt sich sagen, wie lange ein Inserat schon online ist."},"cover_image_absolute_url":{"type":"string","description":"Titelbild, vollständige Adresse. Nur echte Anzeigebilder - nie ein Grundriss oder ein Dokument."},"expose_url":{"type":"string","description":"Die Seite für Menschen. Unverändert weitergeben, nicht kürzen, nicht normalisieren."},"parking_dimensions":{"type":"object","description":"Die Maße eines Stellplatzes in Metern, und nur bei Stellplätzen, Garagen und Carports. line ist Länge × Breite als fertige Zeile, wie das Portal sie setzt - auf eine Nachkommastelle gerundet und ohne die Höhe. Wer genau rechnen muss, nimmt die Einzelfelder: Sie führen zwei Nachkommastellen. Die Zeile entsteht nur, wenn Länge und Breite gefüllt sind. Keine Flächenangabe - die steht in area.","properties":{"length_m":{"type":"number"},"width_m":{"type":"number"},"height_m":{"type":"number"},"line":{"type":"string"}},"required":[],"additionalProperties":false},"pet_policy":{"$ref":"#/components/schemas/AiBenannt"},"amenities":{"type":"object","description":"Ausstattung. available nennt, was vorhanden ist; not_available nur das, was der Anbieter AUSDRÜCKLICH verneint hat. Eine leere not_available-Liste heißt NICHT, dass alles andere vorhanden ist. Fehlt das Feld ganz, wurde nach Ausstattung nie gefragt (Grundstück, Stellplatz).","properties":{"available":{"type":"array","items":{"type":"string"}},"not_available":{"type":"array","items":{"type":"string"}}},"required":["available","not_available"],"additionalProperties":false},"provider":{"type":"object","description":"Der Anbieter: sein öffentlicher Name und seine Art. Über den Prüfstand eines Anbieters sagt diese Adresse nichts; wer ihn braucht, nimmt GET /v1/listings bzw. GET /v1/listings/{listing}.","properties":{"display_name":{"type":"string"},"type":{"$ref":"#/components/schemas/AiBenannt"}},"required":["display_name"],"additionalProperties":false},"description":{"type":"string","description":"Der Text des Anbieters."},"condition":{"$ref":"#/components/schemas/AiBenannt"},"bedrooms":{"type":"number","description":"Schlafzimmer. Derzeit immer eine ganze Zahl."},"bathrooms":{"type":"number","description":"Badezimmer. Derzeit immer eine ganze Zahl."},"floor":{"type":"integer","description":"Etage. 0 ist das Erdgeschoss und eine Angabe, keine Lücke."},"heating_type":{"$ref":"#/components/schemas/AiBenannt"},"year_built":{"type":"integer","description":"Baujahr laut Eckdaten. NICHT dasselbe wie energy.certificate_year - maßgeblich für eine Anzeige ist das Jahr aus dem Energieausweis."},"furnished":{"type":"boolean"},"shared_living":{"type":"boolean","description":"Ob eine Wohngemeinschaft zulässig ist."},"energy":{"type":"object","description":"Die Pflichtangaben nach § 87 Abs. 1 GEG, soweit belegt. certificate_status unterscheidet \"kein Ausweis nötig\", \"Ausweis bestellt, liegt zur Besichtigung vor\" (noch keine Angaben) und \"Ausweis vorhanden, Angaben fehlen\".","properties":{"certificate_status":{"$ref":"#/components/schemas/AiBenannt"},"certificate_type":{"$ref":"#/components/schemas/AiBenannt"},"certificate_year":{"type":"integer","description":"Das im Energieausweis genannte Baujahr (§ 87 Abs. 1 Nr. 4)."},"value_kwh":{"type":"number","description":"Endenergiebedarf bzw. -verbrauch."},"primary_energy_source":{"$ref":"#/components/schemas/AiBenannt"},"power_value_kwh":{"type":"number","description":"Strom-Endenergie beim Nichtwohngebäude; § 87 Abs. 2 GEG verlangt Wärme und Strom getrennt."},"energy_class":{"type":"string","description":"Energieeffizienzklasse; zugleich der Wert für den Filter energy_class."},"disclosure_line":{"type":"string","description":"Dieselben Angaben als fertige Zeile, wie sie das Portal in einer Anzeige setzt."}},"required":[],"additionalProperties":false}},"required":["slug","title","marketing_type","price","costs","area_secondary","expose_url","provider"],"additionalProperties":false},"AiBenannt":{"type":"object","description":"Ein Wert mit beidem: label für den Satz, key für die Anschlussfrage.","properties":{"key":{"type":"string"},"label":{"type":"string"}},"required":["key","label"],"additionalProperties":false},"SearchResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ListingSummary"}},"links":{"type":"object","description":"first, last, prev und next als vollständige Adressen; next ist null auf der letzten Seite."},"meta":{"type":"object","description":"current_page, last_page, per_page, total, from, to, path und links. total zählt höchstens 1000 (die blätterbaren Treffer), die ganze Zahl steht in meta_extras.results_total."},"meta_extras":{"type":"object","properties":{"active_filter_count":{"type":"integer"},"results_total":{"type":"integer","description":"Alle Treffer der Suche. Anders als meta.total nicht bei 1000 gekappt."},"show_results_count":{"type":"boolean"},"headline":{"type":"string","description":"Überschrift der Trefferliste, etwa \"Ergebnisse für Wohnung mieten in Gera\"."},"ignored_filters":{"type":"array","items":{"$ref":"#/components/schemas/IgnoredFilter"},"description":"Was an der Anfrage nicht gewirkt hat. Eine leere Liste heißt: alles hat gewirkt."},"empty_state":{"$ref":"#/components/schemas/EmptyState"}},"required":["active_filter_count","show_results_count","headline","ignored_filters"],"additionalProperties":false}},"required":["data","links","meta","meta_extras"],"additionalProperties":false},"EmptyState":{"type":"object","description":"Nur bei null Treffern: woran es lag und was geht.","properties":{"supply_in_area":{"type":"integer","description":"Wie viele Inserate es im gesuchten Gebiet überhaupt gibt, ohne die übrigen Filter; ohne Kategorie über alle hinweg gezählt. Ohne Ort - auch bei lat und lon ohne radius - zählt die Zahl bundesweit, und next_steps.text sagt es. 0 heißt: Es liegt am Ort. Größer als 0 heißt: Es liegt an den Filtern. Welcher einzelne davon sich lohnt zu lockern, steht in suggestions - bleibt die Liste leer, hilft kein einzelner Filter; ein größerer Umkreis kann es trotzdem tun und steht dann in next_steps.text."},"saved_search_id":{"type":"integer","description":"0, wenn zu diesen Filtern kein Suchauftrag läuft; sonst seine Kennung. Läuft einer, ist er zu bestätigen und nicht ein zweites Mal anzubieten."},"suggestions":{"type":"array","description":"Wege zu Treffern, höchstens drei, der wirksamste zuerst. Ein Weg ohne Treffer steht nicht in der Liste. Ein größerer Umkreis steht nie darunter - brächte er Treffer und gibt es keinen Nachbarort, nennt next_steps.text Entfernung und Zahl; den radius dafür setzt der Klient selbst.","items":{"type":"object","properties":{"kind":{"type":"string","description":"Woran der Weg ansetzt: amenity für ein Ausstattungsmerkmal, price, rooms, area, house_type (Haustyp egal), oder der Name eines der Filter, die es nur hier gibt (etwa energy_class)."},"label":{"type":"string","description":"Der Weg als Satzteil, etwa \"Bis 850 € statt 700 €\"."},"hint":{"type":["string","null"],"description":"Zusatz, wo einer nötig ist, etwa das Preiswort."},"count":{"type":"integer","description":"Wie viele Treffer dieser Weg bringt. Gemessen, nicht geschätzt."},"params":{"type":"object","description":"Unverändert an GET /v1/listings weitergeben; leere Werte und false räumen einen Filter ab und müssen mitgeschickt werden."}},"required":["kind","label","hint","count","params"],"additionalProperties":false}},"nearby":{"type":"array","description":"Nachbarorte mit Angebot, jeder mit Namen, Entfernung und gemessener Trefferzahl. Nur, wenn im gesuchten Gebiet überhaupt nichts steht (supply_in_area ist dann 0) und gesucht wurde über q als Stadtname oder über postal; bei Stadtteil (district_id), Straße oder lat/lon bleibt die Liste leer.","items":{"type":"object","properties":{"name":{"type":"string"},"distance_km":{"type":"number","description":"Entfernung zwischen den Ortsmitten."},"count":{"type":"integer"},"params":{"type":"object","description":"Unverändert an GET /v1/listings weitergeben."}},"required":["name","distance_km","count","params"],"additionalProperties":false}},"next_steps":{"type":"object","description":"Der nächste Schritt: dieselbe Suche auf der Website, wo sie sich als Suchauftrag speichern lässt, dazu ein fertiger Satz. Gibt es weder eine Lockerung noch einen Nachbarort, sagt der Satz das zuerst.","properties":{"web_url":{"type":"string","description":"Dieselbe Suche auf der Website, wo sie sich als Suchauftrag speichern lässt."},"dropped_filters":{"type":"array","items":{"type":"string"},"description":"Filter, die weder der Link noch ein daraus gespeicherter Suchauftrag tragen kann, weil es sie nur hier gibt."},"text":{"type":"string","description":"Der fertige Satz für die Nutzerin, abgestimmt auf die Lage: ob es im Gebiet Angebot gibt, ob ein größerer Umkreis Treffer brächte (dann mit Entfernung und Zahl, sofern nicht schon ein Nachbarort in nearby steht), ob schon ein Suchauftrag läuft und ob jemand angemeldet ist. Zum Weitergeben gedacht - er nennt Filter und Umkreis in Worten, nie als Parameternamen."}},"required":["web_url","dropped_filters","text"],"additionalProperties":false}},"required":["supply_in_area","saved_search_id","suggestions","nearby","next_steps"],"additionalProperties":false},"AiEmptyState":{"type":"object","description":"Nur bei null Treffern: woran es lag und was geht.","properties":{"supply_in_area":{"type":"integer","description":"Wie viele Inserate es im gesuchten Gebiet überhaupt gibt, ohne die übrigen Filter; ohne Kategorie über alle hinweg gezählt. Ohne Ort - auch bei lat und lon ohne radius - zählt die Zahl bundesweit, und next_steps.text sagt es. 0 heißt: Es liegt am Ort. Größer als 0 heißt: Es liegt an den Filtern. Welcher einzelne davon sich lohnt zu lockern, steht in suggestions - bleibt die Liste leer, hilft kein einzelner Filter; ein größerer Umkreis kann es trotzdem tun und steht dann in next_steps.text."},"suggestions":{"type":"array","description":"Wege zu Treffern, höchstens drei, der wirksamste zuerst. Ein Weg ohne Treffer steht nicht in der Liste. Ein größerer Umkreis steht nie darunter - brächte er Treffer und gibt es keinen Nachbarort, nennt next_steps.text Entfernung und Zahl; den radius dafür setzt der Klient selbst.","items":{"type":"object","properties":{"kind":{"type":"string","description":"Woran der Weg ansetzt: amenity für ein Ausstattungsmerkmal, price, rooms, area, house_type (Haustyp egal), oder der Name eines der Filter, die es nur hier gibt (etwa energy_class)."},"label":{"type":"string","description":"Der Weg als Satzteil, etwa \"Bis 850 € statt 700 €\"."},"hint":{"type":["string","null"],"description":"Zusatz, wo einer nötig ist, etwa das Preiswort."},"count":{"type":"integer","description":"Wie viele Treffer dieser Weg bringt. Gemessen, nicht geschätzt."},"params":{"type":"object","description":"Unverändert an GET /v1/listings weitergeben; leere Werte und false räumen einen Filter ab und müssen mitgeschickt werden."}},"required":["kind","label","hint","count","params"],"additionalProperties":false}},"nearby":{"type":"array","description":"Nachbarorte mit Angebot, jeder mit Namen, Entfernung und gemessener Trefferzahl. Nur, wenn im gesuchten Gebiet überhaupt nichts steht (supply_in_area ist dann 0) und gesucht wurde über q als Stadtname oder über postal; bei Stadtteil (district_id), Straße oder lat/lon bleibt die Liste leer.","items":{"type":"object","properties":{"name":{"type":"string"},"distance_km":{"type":"number","description":"Entfernung zwischen den Ortsmitten."},"count":{"type":"integer"},"params":{"type":"object","description":"Unverändert an GET /v1/listings weitergeben."}},"required":["name","distance_km","count","params"],"additionalProperties":false}},"next_steps":{"type":"object","description":"Der nächste Schritt: dieselbe Suche auf der Website, wo sie sich als Suchauftrag speichern lässt, dazu ein fertiger Satz. Gibt es weder eine Lockerung noch einen Nachbarort, sagt der Satz das zuerst.","properties":{"web_url":{"type":"string","description":"Dieselbe Suche auf der Website, wo sie sich als Suchauftrag speichern lässt."},"dropped_filters":{"type":"array","items":{"type":"string"},"description":"Filter, die weder der Link noch ein daraus gespeicherter Suchauftrag tragen kann, weil es sie nur hier gibt."},"text":{"type":"string","description":"Der fertige Satz für die Nutzerin, abgestimmt auf die Lage: ob es im Gebiet Angebot gibt, ob ein größerer Umkreis Treffer brächte (dann mit Entfernung und Zahl, sofern nicht schon ein Nachbarort in nearby steht), ob schon ein Suchauftrag läuft und ob jemand angemeldet ist. Zum Weitergeben gedacht - er nennt Filter und Umkreis in Worten, nie als Parameternamen."}},"required":["web_url","dropped_filters","text"],"additionalProperties":false}},"required":["supply_in_area","suggestions","nearby","next_steps"],"additionalProperties":false},"IgnoredFilter":{"type":"object","properties":{"parameter":{"type":"string","description":"Der Name aus der Anfrage."},"reason":{"type":"string","enum":["unknown_parameter","invalid_value","not_applicable","superseded","adjusted"],"description":"unknown_parameter: Der Name gehört nicht zur Suche. invalid_value: Der Wert passt nicht zum Parameter. not_applicable: Der Parameter wirkt hier nicht so, wie er gemeint ist - er braucht eine Angabe, die fehlt, passt nicht zur Kategorie (house_type ausserhalb der Haussuche, Zimmer bei house_type=mehrfamilienhaus allein), oder er wird anders gelesen als geschickt (ein ganzer Satz in q filtert als Suchwort im Text des Inserats, nicht als Ort). superseded: Ein anderer Parameter legt dasselbe schon fest - entweder weil er die Angabe aussticht (category gegen marketing_type), oder weil zwei Namen dieselbe Grenze setzen (rooms neben rooms_from, area_min neben living_area_from); welche der beiden Angaben gewirkt hat, sagt detail. adjusted: Der Wert wurde auf das Erlaubte gebracht; bei q: Neben dem Ort standen weitere Angaben, gesucht wurde mit dem Ort, und was sich als Filter lesen ließ, steht in applied_filters. In jedem Fall sagt detail, was geschehen ist."},"detail":{"type":"string","description":"Der Grund als Satz."},"applied_filters":{"type":"object","additionalProperties":{"type":"string"},"description":"Nur bei q mit reason adjusted, und nur wenn etwas übernommen wurde: die Angaben aus q als Parameter und Wert, etwa {\"rooms_from\": \"3\"}. Sie wirken wie der gleichnamige Parameter. Übernommen wird nur in einen Parameter, der nicht gesetzt ist (ein gesendetes price_kind=cold zählt als gesetzt), und nur, was zur Kategorie passt; was nicht übernommen wurde, nennt detail."}},"required":["parameter","reason","detail"],"additionalProperties":false},"Listing":{"type":"object","properties":{"id":{"type":"integer"},"slug":{"type":"string"},"title":{"type":"string"},"marketing_type":{"type":"string","enum":["rent","buy"]},"listing_type":{"type":"object","description":"key und label der Objektart."},"property_category":{"type":["string","null"]},"description":{"type":"string"},"price":{"$ref":"#/components/schemas/PriceDetail"},"address":{"type":"object","description":"postal_code, city, district, street, house_number, coordinates, location_precision und visibility_level. Strasse und Hausnummer stehen nur, wo der Anbieter die Adresse freigegeben hat."},"details":{"type":["object","array"],"description":"Angaben zum Objekt: Zimmer, Flächen, Baujahr, Heizung, Energieausweis und weitere."},"area_facts":{"type":["object","array"],"description":"Die Flächen als beschriftete Zeilen, wie sie das Exposé zeigt."},"amenities":{"type":["object","array"],"description":"Ausstattungsmerkmale als Wahrheitswerte."},"amenity_list":{"type":"array","description":"Die gesetzten Ja/Nein-Ausstattungsmerkmale mit key, label, group (Text) und group_key (fester Schlüssel), in der Reihenfolge des Exposés. Nutzungsangaben (möbliert, WG, Haustiere) stehen in details."},"media":{"type":"array","description":"Bilder und Dokumente mit url, thumbnail_url, is_cover und sort_order."},"provider":{"type":"object","description":"Anbieter: display_name, type, organization_name, website, verification_status, is_verified. verification_status trägt verified bei bestätigter Gewerbeprüfung, not_checked bei einem privaten Anbieter und null, wo das Portal nichts aussagt. Ein laufender, offener oder abgelehnter Prüfvorgang ist nicht ablesbar. is_verified false heißt nicht, dass eine Prüfung negativ ausgegangen ist. Telefon und E-Mail nur, soweit freigegeben."},"available_from":{"type":["string","null"]},"available_immediately":{"type":"boolean"},"published_at":{"type":["string","null"]},"expires_at":{"type":["string","null"]},"expose_url":{"type":"string","description":"Die Seite zum Inserat; diese Adresse gehört unverändert in eine Antwort, die auf das Inserat verweist - nicht kürzen, nicht normalisieren. Ohne erkennbare Mobile-App trägt sie den Parameter via=api."},"cover_image_absolute_url":{"type":["string","null"]},"warm_rent":{"type":["object","null"],"description":"Kaltmiete plus Nebenkosten plus Heizkosten, nur bei Wohnungen und Häusern zur Monatsmiete mit sichtbarem Preis und mit Heizkosten-Angabe; sonst null.","properties":{"amount_cents":{"type":"integer","description":"Betrag in Cent."},"currency":{"type":"string"}},"required":["amount_cents","currency"],"additionalProperties":false},"is_favorited":{"type":["boolean","null"],"description":"Nur mit Anmeldung, sonst null."},"inquiry_thread_id":{"type":["integer","null"]},"compliance":{"type":["object","array"],"description":"Pflichtangaben und Hinweise zur Anfrage."},"applicant_wish_fields":{"type":["array","object"],"description":"Angaben, die der Anbieter sich von Interessenten wünscht."},"applicant_self_disclosure":{"type":["array","object"],"description":"Felder der Selbstauskunft, die der Anbieter erfragt."}},"required":["id","slug","title","marketing_type","listing_type","property_category","description","price","address","details","area_facts","amenities","amenity_list","media","provider","available_from","available_immediately","published_at","expires_at","expose_url","cover_image_absolute_url","warm_rent","is_favorited","inquiry_thread_id","compliance","applicant_wish_fields","applicant_self_disclosure"],"additionalProperties":false},"SearchSuggestion":{"type":"object","properties":{"type":{"type":"string","enum":["postal","city","district","region","street"]},"label":{"type":"string"},"subtitle":{"type":["string","null"]},"filters":{"type":"object","description":"q, district_id, postal, lat, lon und near. Unverändert an GET /v1/listings weitergeben, Leerwerte eingeschlossen: Sie löschen den vorherigen Zuschnitt."},"coordinates":{"type":["object","null"],"description":"lat und lon zum Zentrieren einer Karte."}},"required":["type","label","subtitle","filters","coordinates"],"additionalProperties":false},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Feste Kennung des Fehlers, etwa listing_not_found oder validation_failed."},"message":{"type":"string","description":"Der Fehler als Satz. Bei einem abgeschalteten Inserat steht hier derselbe Satz wie auf der Website."},"details":{"type":"object","description":"Nur wo es mehr zu sagen gibt. Je Schlüssel eine Liste: bei validation_failed das Feld mit seinen Meldungen, bei einem abgeschalteten Inserat reason mit dem Eintrag deactivated."}},"required":["code","message"],"additionalProperties":false},"message":{"type":"string","description":"Altfeld für ältere App-Stände, nicht bei jedem Fehler vorhanden. Maßgeblich ist `error`."}},"required":["error"],"additionalProperties":false}}}}