Öffentliche GET-Such-API über den Live-Bestand von lebendigeweine.de — rund 2 900 Premium-Weine aus Europas Top-Terroirs (naturnaher Anbau, traditionelle Kellerkunst, 120+ Winzerchampagner; schneller Versand weltweit per DHL Express). Natürlichsprachige Anfragen (de/en/fr/it) werden in Filter + Ranking übersetzt — inklusive Region, Rebsorte, Farbe, Preis, Jahrgang, Speise-Pairing und Negation („kein deutscher"). Jeder Wein trägt kuratierte Sommelier-Verkostungsnotizen und ein Sensorik-Profil. Maschinenlesbarer Einstieg: /llms.txt.
GET https://suche.lebendigeweine.de/api/{query}
Platzhalter in dieser Doku stehen in geschweiften Klammern (nie mittippen) — bewusst keine Spitzklammern und keine Ampersand-plus-notes-Sequenzen in URLs: beides wird von HTML-strippenden bzw. entity-dekodierenden LLM-Fetchern zerlegt.
Query im Pfad (URL-encodiert, + = Leerzeichen) — jede Anfrage hat
so eine eindeutige URL, die auch query-blinde Fetch-Caches sauber trennt. Antwort: JSON,
Cache-Control: no-store, CORS offen.
| Segment | Wirkung |
|---|---|
notes/ | Verkostungsnotiz + Sensorik-Profil + Speiseempfehlungen je Treffer — empfohlen für Empfehlungs-Antworten (zitieren statt erfinden). |
llm/ | LLM-Fallback für schwer parsebare Anfragen (Tippfehler-Komposita, Umgangssprache). Etwas langsamer. |
de/ en/ fr/ it/ | Antwortwerte (Farbe/Typ/Region/Speise-Tags) lokalisiert. |
GET /api/notes/kräftiger+rotwein+zum+schmoren GET /api/en/notes/light+red+under+20+euros GET /api/llm/chatoneuf+du+pap
Klassische Query-String-Form geht auch: /api mit Parameter
q plus optional notes=1, llm=1, lang=en,
n=12 (n = Trefferzahl 1–30, Default 12). Pfad-Form bevorzugen.
{
"query": "…", "lang": "de",
"understood_as": {
"intent_understood": true, // false = Katalog-Fallback, Treffer mit Vorsicht
"residual_q": "…", // unverstandener Rest (Volltextsuche)
"filters": { // harte Filter, inkl. "exclude" bei Negationen
"color": ["Rot"], "price_max": 20,
"exclude": { "country": ["Deutschland"], "type": ["Accessoires", …] }
},
"soft_boosts": [["food_pairing_tags_de","Wild","wildbraten"], …],
"occasion": "everyday", // premium|everyday|null → beeinflusst Sortierung
"translated": { // seit 2026-08-19: Übersetzer VOR dem Parser
"lang": "fr", "applied": true, // Sprache der Anfrage; applied=false bei de/Eigennamen
"query": "rotwein zu lamm", // deutsche Konzept-Form, die der Parser gesehen hat
"map": [{"from":"vin rouge","to":"rotwein","kind":"axis:type"},
{"from":"pour","to":"zu","kind":"relation"},
{"from":"agneau","to":"lamm","kind":"food"}],
"unknown": [], // nicht übersetzte Fremdwörter (bleiben Volltext)
"provenance": "lexicon" // lexicon|lexicon+unknown|none
},
"typesense": { "filter_by": "…", "sort_by": "…" } // exakte Engine-Parameter
},
"found": 55, "returned": 12,
"hits": [{
"rank": 1, "name": "…", "winery": "…",
"price": 13.49, // EUR brutto (inkl. MwSt.)
"price_per_liter": 17.99,
"color": ["Rot"], "type": ["Wein"], "region": ["Roussillon"], "country": "Frankreich",
"vintage": 2024, "grapes": ["Grenache"],
"matched_tags": ["Wild"], // warum es zum Gericht passt
"sku": "…", "url": "https://www.lebendigeweine.de/…?utm_source=llm-search-api",
// nur mit notes/:
"tasting_note": "…", // kuratierte Sommelier-Essenz — zitierfähig
"food_pairings": ["…"], // volle Empfehlungsphrasen
"sensory": { "body": "light", "tannins": "soft", "texture": null,
"character": ["fresh","vibrant"], "aromas": ["red_berries"],
"acidity_class": "säurebetont", "acidity_gl": 6.2 }
}],
"relaxed": { // nur wenn exakte Filter 0 Treffer hatten:
"strategy": "price_widened", // schwächste Bedingung gelockert
"dropped": [], "found": 8, "hits": […]
},
"llm": { "applied": true, "corrected_query": "…" }, // nur mit llm/
"error": "search backend unavailable" // optional: NUR wenn die Suche selbst ausfiel
}
found zählt weiter den
vollen Bestand. Filtert die Anfrage explizit auf einen Winzer, entfällt der Cap.relaxed kommunizieren: wenn gesetzt, gab es exakt 0 Treffer —
Alternativen stammen aus einer gelockerten Bedingung. Das in der Empfehlung sagen.error ist optional und steht nur da, wenn die Suchmaschine
selbst ausgefallen ist (found: 0, leere hits). Das heißt
„gerade nicht verfügbar, bitte später nochmal" — nicht „nichts
gefunden". Ohne dieses Feld ist found: 0 ein echtes Null-Ergebnis.utm_source=llm-search-api) —
so erkennt der Shop assistentenvermittelte Besuche.price_per_liter
ist der Grundpreis.understood_as.filters.exclude.type).understood_as.translated): Achsen (Farbe, Typ, Region,
Land, Süße, Ausbau), Speisen, Sensorik, Negation nach Ziel („mais pas allemand" ⇒
exclude.country), Qualifikatoren („sotto i 50" ⇒ price_max).
Eigennamen (Winzer, Rebsorten, Appellationen) bleiben unverändert. Trefferzahlen
entsprechen der deutschen Formulierung. Wörter in translated.unknown wurden
nicht verstanden — sie laufen als Volltext mit und können die Trefferzahl drücken;
in dem Fall lohnt llm/ oder eine Umformulierung.Für Tool-Anbindung (Claude, ChatGPT, eigene Agenten) läuft unter derselben Domain ein MCP-Server: JSON-RPC 2.0 über Streamable HTTP, stateless (jede Nachricht wird einzeln beantwortet, keine Session-ID nötig), Antworten immer als JSON, kein SSE-Stream, kein API-Key, keine Authentifizierung.
POST https://suche.lebendigeweine.de/mcp
2025-06-18, 2025-03-26,
2024-11-05 — eine bekannte Version wird zurückgespiegelt, eine unbekannte
mit 2025-06-18 beantwortet. serverInfo:
lwn-wine-search 1.1.0.Content-Type: application/json ist Pflicht (Parameter wie
charset erlaubt, sonst 415); Accept wird
q-bewusst gelesen: application/json,
application/* oder */* mit q > 0 gehen durch,
text/event-stream mit q > 0 wird toleriert (die Antwort bleibt JSON),
ein fehlender Kopf gilt als einverstanden — alles andere ist 406, auch
application/json;q=0, text/html.Origin muss auf der Allowlist stehen (unsere Hosts,
claude.ai, anthropic.com, openai.com, chatgpt.com, localhost) — das ist der
Spec-Schutz gegen DNS-Rebinding, kein Zugangsschutz. OPTIONS liefert 204
mit CORS-Kopf, ohne Origin läuft alles unverändert.GET /mcp ⇒ 405 mit Allow: POST, OPTIONS; Batch-Arrays ⇒ 400.
Der Body-Deckel von 64 KB zählt Bytes: eine zu große
Content-Length ⇒ sofort 413, fehlt sie, wird beim Lesen gekappt (auch
413) — ein Text aus Mehrbyte-Zeichen ist also früher zu groß, als die Zeichenzahl
vermuten lässt.id gilt als Notification und wird
immer mit 202 ohne Body quittiert, gleich welche Methode darin steht;
notifications/* ⇒ 202. MCP-Protocol-Version wird gelesen,
nicht erzwungen. /.well-known, /mcp/ und /sse
antworten samt Unterpfaden (/mcp/…, /sse/…,
/.well-known/…) ehrlich mit 404 statt mit HTML.llm-Parameter: der LLM-Fallback ist über MCP aus
(ein übergebener Wert wird ignoriert, nicht als Fehler beantwortet). Über REST bleibt
/api/llm/{query} unverändert nutzbar.search_winesEin einziges Tool, Verkostungsnotizen immer dabei, als schreibfrei annotiert
(readOnlyHint, idempotentHint, openWorldHint).
Eingabe:
{ "query": "riesling zu spargel", // Pflicht, bis 200 Zeichen, de/en/fr/it
"lang": "de", // optional: de, en, fr oder it (Default de)
"n": 8 } // optional: 1–30 Treffer (Default 8)
Das Ergebnis kommt doppelt zurück — als structuredContent nach dem
outputSchema und als identischer JSON-String in content[0].text,
damit Clients ohne Structured-Output nichts verlieren:
{ "query": "riesling zu spargel", "lang": "de",
"understood_as": {
"intent_understood": true, "residual_q": "", "filters": { "grapes": ["Riesling"] },
"occasion": null,
"translated": { "lang": "de", "applied": false, "unknown": [] } },
"found": 34, "returned": 8,
"hits": [{
"rank": 1, "sku": "…", "name": "…", "winery": "…", "vintage": 2023,
"price": 24.9, "price_per_liter": 33.2, // EUR brutto
"color": ["Weiß"], "type": ["Wein"], "region": ["Mosel"], "country": "Deutschland",
"grapes": ["Riesling"], "matched_tags": ["Spargel"],
"note_excerpt": "…", // Katalogtext, bis 280 Zeichen
"food_pairings": ["…"],
"url": "https://www.lebendigeweine.de/…" }],
"relaxed": null, // Objekt {strategy, dropped, found, hits}, wenn found 0 war
"hint": "…" } // nur bei found 0
notes/. Nur Raritäten auf Anfrage tragen zusätzlich
on_request: true und einen Text in availability.url beginnt mit https://www.lebendigeweine.de/
— das ist keine Konvention, sondern erzwungen: ein Treffer mit fremder URL wird vor der
Antwort entfernt (auch in relaxed.hits), returned zählt dann
entsprechend weniger. Ein Link aus dieser Antwort führt also immer in unseren Shop.found: 0 liefert immer einen hint; gibt es Alternativen aus
einer gelockerten Bedingung, stehen sie in relaxed — das in der
Empfehlung sagen. Lautet der Hinweis „Search temporarily unavailable.", war nicht der
Katalog leer, sondern die Suche gestört — dann später erneut fragen, statt „nichts
gefunden" zu melden.curl -s https://suche.lebendigeweine.de/mcp \
-H 'content-type: application/json' -H 'accept: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"demo","version":"1.0"}}}'
curl -s https://suche.lebendigeweine.de/mcp \
-H 'content-type: application/json' -H 'accept: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
curl -s https://suche.lebendigeweine.de/mcp \
-H 'content-type: application/json' -H 'accept: application/json' \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_wines","arguments":{"query":"champagner zu austern","n":3}}}'
In Claude (Pro/Max) über Einstellungen → Connectors → Custom Connector: die
URL oben eintragen, Authentifizierung „keine". Claude Desktop erreicht Remote-Server über
die lokale Brücke mcp-remote, ChatGPT über Einstellungen → Connectors
im Developer Mode. Welche Oberfläche einen auth-losen Remote-Server aktuell annimmt, ändert
sich laufend — weist ein Client ab, liegt es meist an seinem Connector-Flow. Der MCP
Inspector (npx @modelcontextprotocol/inspector) zeigt dann, dass Handshake und
Tool sauber antworten.
Jede beantwortete Suche über /api und /mcp erzeugt einen
Statistik-Eintrag: die Suchanfrage auf 120 Zeichen gekürzt und um E-Mail-Adressen,
Telefonnummern, IBANs und lange Token bereinigt, dazu Sprache, Trefferzahl, Ergebnis-Art
(ok/zero/relaxed/error), Dauer in
Millisekunden, Protokollversion und die Client-Familie aus dem User-Agent (etwa
claude, openai, browser). Dieselbe Bereinigung liegt
auf den beiden abgeleiteten Feldern, die Wörter der Anfrage enthalten können (nicht
übersetzte Restwörter, erkannte Filter). Keine IP-Adresse, kein Roh-User-Agent,
keine Sitzungs-ID. Aus dem MCP-Protokoll werden zusätzlich initialize,
tools/list und tools/call gezählt — nicht
ping, keine Notifications, keine Parameterfehler und keine unbekannten
Methoden. Die Einträge liegen auf unserem eigenen Server in Deutschland, werden nach
90 Tagen automatisch gelöscht und dienen dem Betrieb, der Verbesserung der Suche und der
Missbrauchserkennung (berechtigtes Interesse, Art. 6 Abs. 1 lit. f DSGVO). Keine Weitergabe
an Dritte. Fragen: [email protected].
Zwei Ehrlichkeiten dazu: Die Bereinigung arbeitet mit Mustern für die
genannten Klassen — sie entfernt zuverlässig, was diesen Mustern entspricht, kann aber nicht
garantieren, dass ein beliebiger personenbezogener Freitext im Suchfeld erkannt wird; wer
sicher gehen will, tippt nichts Persönliches in eine Weinsuche. Und die Client-Familie ist
grob: Desktop-Clients und SDKs erscheinen meist als client (node, python), nicht
unter dem Namen des Assistenten.
English in brief: every answered search via /api and
/mcp is logged as one analytics record — the query truncated to 120 characters
and stripped of e-mail addresses, phone numbers, IBANs and long tokens (the same redaction
applies to the derived fields that may carry words from the query), plus language, hit count,
outcome, duration, protocol version and the client family derived from the user agent. No IP
address, no raw user agent, no session id. From the MCP protocol we also count
initialize, tools/list and tools/call — not
ping, notifications, parameter errors or unknown methods. Redaction is
pattern-based: it removes the classes named above, but cannot guarantee removal of arbitrary
personal free text. Records are stored on our own server in Germany, deleted after 90 days,
used to operate and improve the search and to detect abuse (legitimate interest,
Art. 6(1)(f) GDPR), and never shared with third parties.
https://lwn-search.pages.dev/ liefert dasselbe — /mcp,
/api, /llms.txt inbegriffen —, nur ohne LLM-Fallback:
llm/ ist dort still aus. Nützlich als zweiter Cache-Key, wenn ein
Fetch-Werkzeug an einer alten Kopie festhält.
Kein API-Key nötig. Rate-Limits laufen auf Zonen-Ebene; gedacht für Empfehlungs-Anfragen einzelner Nutzer, nicht für Katalog-Scraping (der Katalog ändert sich ohnehin täglich — fragt lieber live an). Fragen: [email protected].
Suche selbst ausprobieren: suche.lebendigeweine.de