{
  "openapi": "3.1.0",
  "info": {
    "title": "Marina-Team Katalog-API",
    "version": "1.0.0",
    "description": "Öffentliche Lesezugriffe für Dehler- und Varianta-Ersatzteile. Keine Anmeldung und kein API-Schlüssel erforderlich. Texte sind deutschsprachig und können HTML enthalten. Artikel-ID und Artikelnummer sind unterschiedliche Kennungen. Preisangaben erfolgen in EUR; maßgeblich ist priceInfo.effectivePrice aus der Detailabfrage. null bedeutet Preis auf Anfrage. Angebote können zeitlich oder mengenmäßig begrenzt sein; die Bestellprüfung ist maßgeblich."
  },
  "servers": [{ "url": "https://marina-team.de" }],
  "externalDocs": { "description": "Hinweise für AI-Agenten", "url": "https://marina-team.de/llms.txt" },
  "security": [],
  "paths": {
    "/api/ships": {
      "get": {
        "operationId": "listShips",
        "summary": "Öffentliche Bootsmodelle auflisten",
        "description": "Modellcodes aus shortCode exakt als shipCode für die Artikelsuche übernehmen. Kataloglinks haben die Form /Ersatzteile/{shortCode}; für VARIANTA ist /Ersatzteile kanonisch.",
        "responses": {
          "200": {
            "description": "Verfügbare Bootsmodelle in Katalogreihenfolge.",
            "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Ship" } } } }
          }
        }
      }
    },
    "/api/search": {
      "get": {
        "operationId": "searchArticles",
        "summary": "Artikel nach Artikelnummer oder Beschreibung suchen",
        "description": "Liefert höchstens 50 aktive Artikel in Relevanzreihenfolge, auch nicht lieferbare Artikel. Ohne shipCode wird modellübergreifend gesucht. Für verlässliche Preise anschließend getArticle verwenden. Artikel mit overwriteText verwenden diese modellspezifische Beschreibung statt articleBase.description. Links auf Treffer: /Ersatzteile/{ship.shortCode}#{id}, für VARIANTA /Ersatzteile#{id}.",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": true,
            "description": "Artikelnummer oder Suchtext. Darf nicht leer sein oder nur aus Leerzeichen bestehen; mindestens drei Zeichen werden empfohlen.",
            "schema": { "type": "string", "minLength": 1, "pattern": "\\S" },
            "example": "Persenning"
          },
          { "$ref": "#/components/parameters/ShipCode" }
        ],
        "responses": {
          "200": {
            "description": "Suchtreffer; eine leere Liste bedeutet keine Treffer. Die Ergebnisliste ist auf 50 Einträge begrenzt und bietet keine Paginierung.",
            "content": { "application/json": { "schema": { "type": "array", "maxItems": 50, "items": { "$ref": "#/components/schemas/ArticleSummary" } } } }
          },
          "400": { "description": "Der erforderliche Suchtext fehlt oder ist leer." }
        }
      }
    },
    "/api/articles/{id}": {
      "get": {
        "operationId": "getArticle",
        "summary": "Verfügbaren Artikel mit aktuellem Einzelpreis lesen",
        "description": "Verwendet die Artikel-ID aus einem Suchtreffer, nicht die Artikelnummer. Nur aktive und lieferbare Artikel in öffentlichen Katalogen werden zurückgegeben. priceInfo.effectivePrice berücksichtigt die Preisberechnung für das Bootsmodell; die endgültige Prüfung zeitlich oder mengenmäßig begrenzter Angebote erfolgt bei der Bestellung.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Artikel-ID aus id eines Suchtreffers. Als URL-Pfadsegment kodieren.",
            "schema": { "type": "string", "minLength": 1 }
          }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ArticleDetails" },
          "404": { "$ref": "#/components/responses/ArticleNotFound" }
        }
      }
    },
    "/api/articles/by-number/{matnr}": {
      "get": {
        "operationId": "getArticleByNumber",
        "summary": "Verfügbaren Artikel anhand seiner exakten Artikelnummer lesen",
        "description": "Bevorzugt einen Katalogeintrag des angegebenen Bootsmodells. Gibt es dafür keinen verfügbaren Eintrag, wird ein anderer öffentlicher Katalogeintrag derselben Artikelnummer zurückgegeben. shipCode ist hier eine Präferenz, kein strikter Filter; ship.shortCode der Antwort prüfen. Für einen bestimmten Suchtreffer getArticle mit dessen id verwenden.",
        "parameters": [
          {
            "name": "matnr",
            "in": "path",
            "required": true,
            "description": "Exakte Artikelnummer aus articleBase.matnr, als URL-Pfadsegment kodiert.",
            "schema": { "type": "string", "minLength": 1 }
          },
          {
            "name": "shipCode",
            "in": "query",
            "required": false,
            "description": "Bevorzugter Modellcode aus listShips. Ein Rückfall auf ein anderes Modell ist möglich; ship.shortCode der Antwort prüfen.",
            "schema": { "type": "string" },
            "example": "VARIANTA"
          }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ArticleDetails" },
          "404": { "$ref": "#/components/responses/ArticleNotFound" }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "ShipCode": {
        "name": "shipCode",
        "in": "query",
        "required": false,
        "description": "Optionaler Modellcode aus listShips; exakt übernehmen. Ohne Parameter werden alle öffentlichen Modelle durchsucht.",
        "schema": { "type": "string" },
        "example": "VARIANTA"
      }
    },
    "responses": {
      "ArticleDetails": {
        "description": "Artikel mit modellspezifischer Beschreibung und Preisberechnung in EUR.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ArticleDetails" } } }
      },
      "ArticleNotFound": { "description": "Artikel existiert nicht, ist nicht aktiv, nicht lieferbar oder gehört keinem öffentlichen Katalog an." }
    },
    "schemas": {
      "Ship": {
        "type": "object",
        "required": ["shortCode", "longName"],
        "properties": {
          "shortCode": { "type": "string", "description": "Modellcode für Suchfilter und Kataloglinks.", "examples": ["VARIANTA"] },
          "longName": { "type": "string", "description": "Anzeigename des Bootsmodells." },
          "description": { "type": "string", "description": "Modellbeschreibung; kann HTML enthalten." },
          "isDehler": { "type": "boolean" },
          "orderIndex": { "type": "integer" }
        }
      },
      "ArticleBase": {
        "type": "object",
        "required": ["matnr", "description", "isDeliverable"],
        "properties": {
          "matnr": { "type": "string", "description": "Artikelnummer; unterscheidet sich von der Artikel-ID." },
          "description": { "type": "string", "description": "Grundbeschreibung. Ein befülltes overwriteText des Katalogeintrags hat Vorrang." },
          "price": { "type": ["number", "null"], "description": "Grundpreis in EUR, ohne modellspezifische Angebote. Für Auskünfte priceInfo.effectivePrice aus der Detailabfrage verwenden." },
          "isDeliverable": { "type": "boolean", "description": "Lieferbarkeitskennzeichen; keine konkrete Lagerbestandszahl." },
          "isChemical": { "type": "boolean" },
          "attachment": { "type": ["string", "null"], "description": "Dokumentpfad relativ zur Website-Wurzel. Mit https://marina-team.de/ auflösen; führenden Schrägstrich berücksichtigen." },
          "attachmentThumbnail": { "type": ["string", "null"] }
        }
      },
      "ArticleSummary": {
        "type": "object",
        "required": ["id", "articleBase", "ship"],
        "properties": {
          "id": { "type": "string", "description": "ID des modellspezifischen Katalogeintrags für getArticle und den HTML-Anker." },
          "overwriteText": { "type": ["string", "null"], "description": "Modellspezifische Beschreibung; verwenden, wenn nicht leer." },
          "orderIndex": { "type": ["integer", "null"] },
          "articleBase": { "$ref": "#/components/schemas/ArticleBase" },
          "ship": { "$ref": "#/components/schemas/Ship" }
        }
      },
      "ArticleDetails": {
        "allOf": [
          { "$ref": "#/components/schemas/ArticleSummary" },
          {
            "type": "object",
            "required": ["priceInfo"],
            "properties": {
              "priceInfo": {
                "type": "object",
                "required": ["effectivePrice", "originalPrice", "isAngebot", "showStrikethrough", "gueltigBis"],
                "properties": {
                  "effectivePrice": { "type": ["number", "null"], "description": "Berechneter Einzelpreis in EUR, auf zwei Nachkommastellen gerundet. null bedeutet Preis auf Anfrage; 0 ist ein numerischer Preis." },
                  "originalPrice": { "type": ["number", "null"], "description": "Vergleichspreis in EUR, sofern vorhanden." },
                  "isAngebot": { "type": "boolean", "description": "Die Preisberechnung verwendet ein Angebot; Mengenbegrenzungen werden bei der Bestellung geprüft." },
                  "showStrikethrough": { "type": "boolean" },
                  "gueltigBis": { "type": ["string", "null"], "format": "date", "description": "Angebotsende im Format YYYY-MM-DD, sofern gesetzt." }
                }
              }
            }
          }
        ]
      }
    }
  }
}
