{
  "openapi": "3.0.3",
  "info": {
    "title": "immonika Listings API",
    "version": "1.0",
    "description": "Oeffentliche, read-only Listings- und Makler-API als statische JSON-Dateien. Kein API-Key. Einstieg: /api/listings.json, /api/cities.json, /{ortSlug}/api/listings.json, /{ortSlug}/api/stadtteile.json, /{ortSlug}/api/makler.json. Stadtteil-Filter: items[].district oder items[].stadtteil. Doku: https://immonika.de/api/docs/",
    "termsOfService": "https://immonika.de/agb.html",
    "contact": {
      "url": "https://immonika.de/info.html"
    }
  },
  "externalDocs": {
    "description": "Menschenlesbare API-Dokumentation (DE)",
    "url": "https://immonika.de/api/docs/"
  },
  "servers": [
    { "url": "https://immonika.de" }
  ],
  "tags": [
    { "name": "discovery", "description": "Einstieg und Verweise" },
    { "name": "cities", "description": "Orte mit mindestens einem Inserat" },
    { "name": "listings", "description": "Inserate pro Ort oder Filter-Pfad" },
    { "name": "districts", "description": "Stadtteile/Bezirke pro Ort (Centroid-Liste)" },
    { "name": "makler", "description": "Empfohlene Makler pro Ort (Orts-Anbieterseite)" },
    { "name": "exposee", "description": "Einzel-Exposee (listing.json, contactIntent)" },
    { "name": "contact", "description": "Kontaktanfrage an Anbieter (service.immonika.de; Felder aus contactIntent)" }
  ],
  "paths": {
    "/api/listings.json": {
      "get": {
        "tags": ["discovery"],
        "summary": "API-Discovery (kein Gesamt-Feed)",
        "description": "Liefert type=discovery mit Verweisen auf cities.json, ai-index.json und llms.txt. Enthaelt keine items-Liste mit Exposees.",
        "operationId": "getListingsDiscovery",
        "responses": {
          "200": {
            "description": "Discovery-Stub",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DiscoveryResponse" }
              }
            }
          }
        }
      }
    }
,
    "/api/listings": {
      "get": {
        "tags": ["discovery"],
        "summary": "API-Discovery-Alias (ohne .json)",
        "description": "Gleicher Discovery-Stub wie /api/listings.json. Fuer Scanner, die REST-Pfade ohne Dateiendung erwarten. Produktion: 301 auf /api/listings.json moeglich.",
        "operationId": "getListingsDiscoveryAlias",
        "responses": {
          "200": {
            "description": "Discovery-Stub",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DiscoveryResponse" }
              }
            }
          }
        }
      }
    }
,
    "/api/makler.json": {
      "get": {
        "tags": ["discovery"],
        "summary": "Makler-API-Discovery (kein Gesamt-Feed)",
        "description": "Liefert type=discovery mit Verweis auf perCityMaklerApiPattern. Enthaelt keine items-Liste mit Maklern.",
        "operationId": "getMaklerDiscovery",
        "responses": {
          "200": {
            "description": "Discovery-Stub",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DiscoveryResponse" }
              }
            }
          }
        }
      }
    }
,
    "/api/cities.json": {
      "get": {
        "tags": ["cities"],
        "summary": "Alle Orte mit Inseraten",
        "description": "Authoritative Liste aktiver Orte inkl. listingCount und Ziel-URL fuer Ort-Listings.",
        "operationId": "getCities",
        "responses": {
          "200": {
            "description": "Stadtliste",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CitiesResponse" }
              }
            }
          }
        }
      }
    }
,
    "/{ortSlug}/api/listings.json": {
      "get": {
        "tags": ["listings"],
        "summary": "Inserate eines Ortes",
        "description": "Schema.org RealEstateListing in items. Stadtteil in district, stadtteil, address.addressSubLocality und name. Auf Ortsebene: stadtteileApi. maxItems 500 auf Ort- und Vermarktungsart-Ebene.",
        "operationId": "getListingsByCity",
        "parameters": [
          {
            "name": "ortSlug",
            "in": "path",
            "required": true,
            "description": "URL-Slug aus /api/cities.json (Feld url)",
            "schema": { "type": "string", "example": "Berlin" }
          }
        ],
        "responses": {
          "200": {
            "description": "Listings oder leeres items bei Ort ohne Inserate",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ListingsResponse" }
              }
            }
          }
        }
      }
    }
,
    "/{ortSlug}/api/stadtteile.json": {
      "get": {
        "tags": ["districts"],
        "summary": "Stadtteile/Bezirke eines Ortes",
        "description": "Liste anzeigbarer Stadtteile mit Centroid (lat/lng). Fuer KI-Filter: items in /{ortSlug}/api/listings.json nach district, stadtteil oder address.addressSubLocality filtern.",
        "operationId": "getDistrictsByCity",
        "parameters": [
          {
            "name": "ortSlug",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "example": "Berlin" }
          }
        ],
        "responses": {
          "200": {
            "description": "Stadtteilliste oder leeres items[]",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/StadtteileResponse" }
              }
            }
          }
        }
      }
    }
,
    "/{ortSlug}/{vermarktungsart}/api/listings.json": {
      "get": {
        "tags": ["listings"],
        "summary": "Gefilterte Inserate (Vermarktungsart)",
        "description": "vermarktungsart: Kaufen, Mieten oder Pachten. Weitere Ebenen: /{objektart}/api/listings.json und /{unterkategorie}/api/listings.json.",
        "operationId": "getListingsByCityAndMarketing",
        "parameters": [
          { "name": "ortSlug", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "vermarktungsart", "in": "path", "required": true, "schema": { "type": "string", "enum": ["Kaufen", "Mieten", "Pachten"] } }
        ],
        "responses": {
          "200": {
            "description": "Gefilterte Listings",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ListingsResponse" }
              }
            }
          }
        }
      }
    }
,
    "/{ortSlug}/api/makler.json": {
      "get": {
        "tags": ["makler"],
        "summary": "Empfohlene Makler eines Ortes",
        "description": "RealEstateAgent-Liste fuer Orts-Anbieterseite (20 km Umkreis). Sortierung: ranking absteigend. Parallel zu /{ortSlug}/api/listings.json. Alias: /{ortSlug}/api/agents.json",
        "operationId": "getMaklerByCity",
        "parameters": [
          {
            "name": "ortSlug",
            "in": "path",
            "required": true,
            "description": "URL-Slug des Ortes (z. B. aus /api/cities.json)",
            "schema": { "type": "string", "example": "Frankfurt-am-Main" }
          }
        ],
        "responses": {
          "200": {
            "description": "Maklerliste oder leeres items[]",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MaklerListResponse" }
              }
            }
          }
        }
      }
    }
,
    "/{ortSlug}/Makler/api/makler.json": {
      "get": {
        "tags": ["makler"],
        "summary": "Empfohlene Makler (Makler-Ordner-Alias)",
        "description": "Identischer Inhalt wie /{ortSlug}/api/makler.json. Kontext Orts-Anbieterseite. Alias: agents.json",
        "operationId": "getMaklerByCityMaklerOrdner",
        "parameters": [
          {
            "name": "ortSlug",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "example": "Frankfurt-am-Main" }
          }
        ],
        "responses": {
          "200": {
            "description": "Maklerliste oder leeres items[]",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MaklerListResponse" }
              }
            }
          }
        }
      }
    }
,
    "/{ortSlug}/{vermarktungsart}/{objektart}/{unterkategorie}/{exposeeId}/api/listing.json": {
      "get": {
        "tags": ["exposee"],
        "summary": "Exposee-Detail (Einzelobjekt)",
        "description": "Schema.org RealEstateListing mit parentFeed, provider/broker agentApi und contactIntent (DOI/Captcha, automatable: false). Nicht in sitemap-api.xml (Skalierung).",
        "operationId": "getExposeeListingDetail",
        "parameters": [
          { "name": "ortSlug", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "vermarktungsart", "in": "path", "required": true, "schema": { "type": "string", "example": "Kaufen" } },
          { "name": "objektart", "in": "path", "required": true, "schema": { "type": "string", "example": "Eigentumswohnungen" } },
          { "name": "unterkategorie", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "exposeeId", "in": "path", "required": true, "schema": { "type": "string", "description": "Exposee-Ordner-Hash" } }
        ],
        "responses": {
          "200": {
            "description": "Exposee-Detail-JSON",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ListingDetailResponse" }
              }
            }
          }
        }
      }
    }
,
    "/{ortSlug}/{vermarktungsart}/{objektart}/{unterkategorie}/{exposeeId}/api/contact-request.json": {
      "get": {
        "tags": ["exposee", "contact"],
        "summary": "Kontakt-Schema pro Exposée (JSON-POST-Mapping)",
        "description": "GET: Schema, jsonFieldMap, exposeeContext, exampleRequest. POST JSON an service.immonika.de/kontakt.php (siehe post.url in Antwort).",
        "operationId": "getExposeeContactRequestSchema",
        "parameters": [
          { "name": "ortSlug", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "vermarktungsart", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "objektart", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "unterkategorie", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "exposeeId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "contactRequestApi JSON", "content": { "application/json": { "schema": { "type": "object" } } } }
        }
      }
    }
,
    "/kontakt.php": {
      "post": {
        "tags": ["contact"],
        "summary": "Kontaktanfrage (Exposee → Anbieter)",
        "description": "Host: https://service.immonika.de (nicht immonika.de). Felder und Flows aus contactIntent in GET .../api/listing.json. Content-Type application/x-www-form-urlencoded. JSON-Antwort: Accept application/json oder mode=agent. Status: sent, doi_required, consent_required, duplicate_exposee, rate_limited, validation_error.",
        "operationId": "postKontaktAnfrage",
        "servers": [ { "url": "https://service.immonika.de" } ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "firstName": { "type": "string" }, "lastName": { "type": "string" },
                  "street": { "type": "string" }, "houseNumber": { "type": "string" },
                  "postalCode": { "type": "string" }, "city": { "type": "string" },
                  "email": { "type": "string", "format": "email" },
                  "message": { "type": "string" }, "consent": { "type": "boolean" },
                  "vermittlerTyp": { "type": "string" },
                  "exposeeContext": { "type": "object" }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": ["Vorname", "Nachname", "Strasse", "Hausnummer", "PLZ", "Ort", "Email", "aid", "anr", "onr", "url"],
                "properties": {
                  "Vorname": { "type": "string" },
                  "Nachname": { "type": "string" },
                  "Strasse": { "type": "string" },
                  "Hausnummer": { "type": "string" },
                  "PLZ": { "type": "string" },
                  "Ort": { "type": "string" },
                  "Email": { "type": "string", "format": "email" },
                  "Nachricht": { "type": "string" },
                  "Einverstaendnis": { "type": "string", "enum": ["yes"], "description": "Pflicht Erstkontakt; optional bei consentCached" },
                  "VermittlerTyp": { "type": "string", "example": "ki-agent" },
                  "mode": { "type": "string", "enum": ["agent"], "description": "JSON-Antwort aktivieren" },
                  "aid": { "type": "string" }, "anr": { "type": "string" }, "onr": { "type": "string" },
                  "otx": { "type": "string" }, "ot": { "type": "string" }, "url": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "JSON status sent oder doi_required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContactAgentResponse" } } } },
          "400": { "description": "JSON Fehlerstatus", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContactAgentResponse" } } } }
        }
      }
    }
,
    "/api/openapi.json": {
      "get": {
        "tags": ["discovery"],
        "summary": "Diese OpenAPI-Spezifikation",
        "operationId": "getOpenApiSpec",
        "responses": {
          "200": {
            "description": "OpenAPI 3.0 JSON",
            "content": { "application/json": { "schema": { "type": "object" } } }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "DiscoveryResponse": {
        "type": "object",
        "required": ["schemaVersion", "type", "entryPoints"],
        "properties": {
          "schemaVersion": { "type": "string" },
          "generatedAt": { "type": "string", "format": "date-time" },
          "type": { "type": "string", "enum": ["discovery"] },
          "description": { "type": "string" },
          "entryPoints": { "type": "object" },
          "perCityListingsPattern": { "type": "string" },
          "perCityStadtteilePattern": { "type": "string" },
          "hint": { "type": "string" }
        }
      },
      "CitiesResponse": {
        "type": "object",
        "required": ["schemaVersion", "totalCount", "items"],
        "properties": {
          "schemaVersion": { "type": "string" },
          "generatedAt": { "type": "string", "format": "date-time" },
          "totalCount": { "type": "integer" },
          "items": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/CityItem" }
          }
        }
      },
      "CityItem": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "postalCode": { "type": "string" },
          "listingCount": { "type": "integer" },
          "url": { "type": "string", "description": "Relativer Pfad zu Ort-Listings" }
        }
      },
      "ListingsResponse": {
        "type": "object",
        "required": ["schemaVersion", "totalCount", "items"],
        "properties": {
          "schemaVersion": { "type": "string" },
          "generatedAt": { "type": "string", "format": "date-time" },
          "totalCount": { "type": "integer" },
          "truncated": { "type": "boolean" },
          "maxItems": { "type": "integer" },
          "items": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/RealEstateListing" }
          }
        }
      },
      "MaklerListResponse": {
        "type": "object",
        "required": ["schemaVersion", "type", "totalCount", "items"],
        "properties": {
          "schemaVersion": { "type": "string" },
          "generatedAt": { "type": "string", "format": "date-time" },
          "type": { "type": "string", "enum": ["maklerList"] },
          "place": { "type": "object" },
          "radiusKm": { "type": "number" },
          "totalCount": { "type": "integer" },
          "truncated": { "type": "boolean" },
          "maxItems": { "type": "integer" },
          "items": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/MaklerAgentItem" }
          }
        }
      },
      "MaklerAgentItem": {
        "type": "object",
        "properties": {
          "@type": { "type": "string", "enum": ["RealEstateAgent"] },
          "name": { "type": "string" },
          "url": { "type": "string" },
          "ranking": { "type": "integer" },
          "listingCount": { "type": "integer" },
          "listingCountInArea": { "type": "integer" },
          "agentApi": { "type": "string" }
        }
      },
      "RealEstateListing": {
        "type": "object",
        "properties": {
          "@type": { "type": "string", "example": "RealEstateListing" },
          "name": { "type": "string", "description": "Titel inkl. Stadtteil, z. B. Haeuser in Berlin · Friedrichsfelde" },
          "district": { "type": "string", "description": "Stadtteil/Bezirk (engl. Alias, gleicher Wert wie stadtteil)" },
          "stadtteil": { "type": "string", "description": "Stadtteil/Bezirk (deutsch, gleicher Wert wie district)" },
          "url": { "type": "string" },
          "offers": {
            "type": "object",
            "properties": {
              "@type": { "type": "string" },
              "price": { "type": "number" },
              "priceCurrency": { "type": "string" },
              "availability": { "type": "string" }
            }
          },
          "numberOfRooms": { "type": "number" },
          "floorSize": { "type": "object" },
          "yearBuilt": { "type": "integer" },
          "datePosted": { "type": "string", "format": "date" },
          "address": { "$ref": "#/components/schemas/PostalAddress" }
        }
      },
      "PostalAddress": {
        "type": "object",
        "properties": {
          "@type": { "type": "string", "example": "PostalAddress" },
          "addressLocality": { "type": "string", "description": "Ort/Stadt" },
          "postalCode": { "type": "string" },
          "addressSubLocality": { "type": "string", "description": "Stadtteil/Bezirk (Schema.org)" }
        }
      },
      "StadtteileResponse": {
        "type": "object",
        "required": ["schemaVersion", "ort", "totalCount", "items"],
        "properties": {
          "schemaVersion": { "type": "string" },
          "generatedAt": { "type": "string", "format": "date-time" },
          "ort": { "type": "string", "description": "Anzeigename des Ortes" },
          "totalCount": { "type": "integer" },
          "items": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/StadtteilItem" }
          }
        }
      },
      "StadtteilItem": {
        "type": "object",
        "properties": {
          "name": { "type": "string", "description": "Stadtteil/Bezirk — Filterwert fuer listings.json" },
          "lat": { "type": "number" },
          "lng": { "type": "number" }
        }
      },
      "ListingDetailResponse": {
        "type": "object",
        "required": ["schemaVersion", "type", "@type", "name", "url", "listingApi"],
        "properties": {
          "schemaVersion": { "type": "string" },
          "generatedAt": { "type": "string", "format": "date-time" },
          "type": { "type": "string", "enum": ["listingDetail"] },
          "@type": { "type": "string", "enum": ["RealEstateListing"] },
          "name": { "type": "string" },
          "url": { "type": "string" },
          "humanPage": { "type": "string" },
          "listingApi": { "type": "string" },
          "parentFeed": { "type": "string" },
          "description": { "type": "string" },
          "image": { "type": "string" },
          "images": { "type": "array", "items": { "type": "string" } },
          "offers": { "type": "object" },
          "address": { "$ref": "#/components/schemas/PostalAddress" },
          "provider": { "type": "object" },
          "broker": { "type": "object" },
          "contactIntent": { "$ref": "#/components/schemas/ContactIntent" }
        }
      },
      "ContactIntent": {
        "type": "object",
        "properties": {
          "schemaVersion": { "type": "string", "example": "3.0" },
          "contactRequestApi": { "type": "string", "description": "Pfad zu api/contact-request.json" },
          "fieldDefinitions": { "type": "array", "items": { "type": "object" } },
          "agentWorkflow": { "type": "array", "items": { "type": "object" } },
          "exampleRequest": { "type": "object" },
          "jsonRequestContentType": { "type": "string", "example": "application/json" },
          "type": { "type": "string", "enum": ["contactIntent"] },
          "endpoint": { "type": "string", "example": "https://service.immonika.de/kontakt.php" },
          "method": { "type": "string", "enum": ["POST"] },
          "automatable": { "type": "boolean", "description": "false — Erstkontakt erfordert DOI und Einwilligung" },
          "prefillGetUrl": { "type": "string" },
          "optionalFieldVermittlerTyp": { "type": "string", "example": "ki-agent" },
          "consentTextVersion": { "type": "string", "example": "v2" },
          "consentField": { "type": "string", "example": "Einverstaendnis=yes" },
          "jsonAcceptHeader": { "type": "string", "example": "application/json" },
          "jsonStatusValues": { "type": "array", "items": { "type": "string" }, "example": ["sent", "doi_required", "consent_required", "duplicate_exposee", "rate_limited", "validation_error"] },
          "flows": {
            "type": "object",
            "properties": {
              "firstContact": { "type": "object", "description": "humanStepsRequired: email_doi_confirmation, explicit_consent_forwarding" },
              "verifiedEmail": { "type": "object", "description": "Allowlist; consentCached; limits perHour 120, per24h 400, perExposeeDays 7" }
            }
          },
          "fields": { "type": "array", "items": { "type": "string" } },
          "potentialAction": { "type": "object", "description": "Schema.org ContactAction in listing.json" }
        }
      },
      "ContactAgentResponse": {
        "type": "object",
        "properties": {
          "status": { "type": "string", "enum": ["sent", "doi_required", "consent_required", "duplicate_exposee", "rate_limited", "validation_error"] },
          "message": { "type": "string" }
        }
      }
    }
  }
}
