KI-Suche als API — deine UI, unsere Relevanz.
Jede Antwort ist bereits gewichtet und kategorisiert: Top-Treffer für Teaser, Liste für den Rest, Gruppen für Filter und „Häufig gesucht“ als Tags. Titel, Kurztext, Bild und URL kommen mit.
1 · Widget (Copy & Paste)
Volle Funktionalität ohne Code — Aussehen und Verhalten steuerst du im Backend, Feinschliff per Custom CSS.
<script src="https://cdn.querrio.com/widget.js"
data-key="pk_dein_browser_key" defer></script>2 · JS-SDK für eigene UIs
Rund 2 KB, ohne Abhängigkeiten. Enthält Debouncing, Session-Handling und Tracking.
<script src="https://cdn.querrio.com/sdk.js"></script>
<script>
const asky = querrio.createClient({ key: 'pk_dein_browser_key' });
const res = await asky.search('beispiel suchanfrage');
// res.top_hits → 1–2 Treffer für die Teaser-Darstellung
// res.results → restliche Treffer als Liste
// res.groups → Kategorien mit Anzahl und Treffer-IDs
// res.suggestions → "Häufig gesucht" als Tags
// res.intent → erkannte Absicht, Zeitraum, Zielgruppe
asky.trackClick(res.results[0], 1);
</script>TypeScript-Typen
Die vollständigen Typen (inkl. Kategorien, Tier-Gewichtung, Intent und Debug-Objekt) liegen unter /sdk.d.ts. Datei ablegen oder per curl ins Projekt ziehen — danach greifen Autocompletion und Typprüfung für SDK und REST-Antworten.
curl -o types/asky.d.ts https://cdn.querrio.com/sdk.d.ts
// app.ts
/// <reference path="./types/asky.d.ts" />
const asky = window.Querrio.createClient({ key: 'pk_dein_browser_key' });
const res: querrio.SearchResponse = await asky.search('beispiel suchanfrage');
res.top_hits[0].tier; // "top" | "list"
res.groups[0].category; // "event" | "article" | "product" | …
res.intent.date_from; // "2026-04-19" oder null
asky.trackClick(res.results[0], 1);Eigene UI – komplette Beispiele
Statt des Widgets deine eigene Oberfläche: Suchfeld mit Vorschlägen, Top-Treffer mit Bild, Kategorie-Filter, „Häufig gesucht“, Hinweis bei null Treffern und Klick-Auswertung. Kopieren, Schlüssel eintragen, fertig.
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<title>Eigene Suche mit querrio</title>
<script src="https://cdn.querrio.com/sdk.js"></script>
<style>
body { font-family: system-ui, sans-serif; max-width: 720px; margin: 40px auto; padding: 0 16px; }
input { width: 100%; padding: 12px 16px; border: 1px solid #ccc; border-radius: 999px; font-size: 16px; }
.suggest { list-style: none; padding: 0; margin: 4px 0; border: 1px solid #eee; border-radius: 12px; }
.suggest li { padding: 8px 14px; cursor: pointer; }
.suggest li:hover { background: #f4f4f5; }
.chips button { margin: 4px 4px 0 0; padding: 4px 12px; border: 1px solid #ddd; border-radius: 999px; background: #fff; cursor: pointer; }
.chips button.active { background: #111; color: #fff; }
.hit { display: flex; gap: 12px; padding: 12px 0; border-bottom: 1px solid #eee; text-decoration: none; color: inherit; }
.hit img { width: 96px; height: 64px; object-fit: cover; border-radius: 8px; }
.top { background: #f7f7f8; border-radius: 12px; padding: 12px; }
small { color: #666; }
</style>
</head>
<body>
<input id="q" type="search" placeholder="Wonach suchst du?" autocomplete="off" />
<ul id="suggest" class="suggest" hidden></ul>
<div id="filters" class="chips"></div>
<div id="out"></div>
<p><small>Suche von querrio</small></p>
<script>
const asky = querrio.createClient({ key: "pk_dein_browser_key" });
const $ = (id) => document.getElementById(id);
let last = null; // letzte Antwort
let activeCat = null; // gewählter Kategorie-Filter
let timer;
// Vorschläge beim Tippen
$("q").addEventListener("input", () => {
clearTimeout(timer);
const q = $("q").value.trim();
if (q.length < 2) { $("suggest").hidden = true; return; }
timer = setTimeout(async () => {
const s = await asky.suggest(q);
if (!s.enabled || !s.items.length) { $("suggest").hidden = true; return; }
$("suggest").innerHTML = s.items
.map((i) => `<li data-text="${i.text}">${i.text}</li>`).join("");
$("suggest").hidden = false;
}, 150);
});
$("suggest").addEventListener("click", (e) => {
const text = e.target.dataset.text;
if (!text) return;
asky.trackSuggestClick(text);
$("q").value = text;
$("suggest").hidden = true;
run(text);
});
$("q").addEventListener("keydown", (e) => {
if (e.key === "Enter") { $("suggest").hidden = true; run($("q").value); }
});
async function run(query) {
if (!query.trim()) return;
$("out").innerHTML = "<small>Suche läuft …</small>";
activeCat = null;
last = await asky.search(query);
render();
}
function hitHtml(h, pos, top) {
return `<a class="hit ${top ? "top" : ""}" href="${h.url}" data-pos="${pos}" data-id="${h.id}">
${h.image_url ? `<img src="${h.image_url}" alt="" />` : ""}
<div><strong>${h.title}</strong><br /><small>${h.snippet ?? ""}</small></div>
</a>`;
}
function render() {
const r = last;
const all = [...r.top_hits, ...r.results];
// Keine Treffer: vorgeschlagene Begriffe anbieten
if (!all.length) {
$("filters").innerHTML = "";
$("out").innerHTML = `<p>Noch nichts Passendes gefunden. Versuch es mit:</p>
<div class="chips">${r.suggestions.map((s) => `<button data-q="${s}">${s}</button>`).join("")}</div>`;
return;
}
// Kategorie-Filter aus den Gruppen
$("filters").innerHTML = r.groups.map((g) =>
`<button data-cat="${g.category}" class="${g.category === activeCat ? "active" : ""}">${g.label} (${g.count})</button>`
).join("");
const visible = activeCat
? all.filter((h) => r.groups.find((g) => g.category === activeCat)?.hit_ids.includes(h.id))
: null;
$("out").innerHTML =
(r.date_label ? `<small>Zeitraum: ${r.date_label}</small>` : "") +
(visible
? visible.map((h, i) => hitHtml(h, i + 1, false)).join("")
: r.top_hits.map((h, i) => hitHtml(h, i + 1, true)).join("") +
r.results.map((h, i) => hitHtml(h, r.top_hits.length + i + 1, false)).join("")) +
(r.suggestions.length
? `<p><small>Häufig gesucht</small></p><div class="chips">${r.suggestions
.map((s) => `<button data-q="${s}">${s}</button>`).join("")}</div>`
: "");
}
// Filter- und Stichwort-Klicks
document.addEventListener("click", (e) => {
const t = e.target.closest("button");
if (!t) return;
if (t.dataset.cat) { activeCat = activeCat === t.dataset.cat ? null : t.dataset.cat; render(); }
if (t.dataset.q) { $("q").value = t.dataset.q; run(t.dataset.q); }
});
// Klick-Auswertung: Analytics + Autopilot lernen mit
$("out").addEventListener("click", (e) => {
const a = e.target.closest("a.hit");
if (!a || !last) return;
const hit = [...last.top_hits, ...last.results].find((h) => h.id === a.dataset.id);
if (hit) asky.trackClick(hit, Number(a.dataset.pos));
});
</script>
</body>
</html>Checkliste vor dem Livegang
- Im Browser nur den Browser-Schlüssel (
pk_…) verwenden und deine Domain im Backend unter „API & Widget“ freigeben. - Server-Schlüssel ausschließlich auf deinem Server nutzen – nie im Browser-Code.
- Klick-Auswertung einbauen – ohne sie lernen Analytics und Autopilot nicht mit.
- Den Hinweis „Suche von querrio“ gemäß deinem Abo sichtbar lassen.
3 · REST direkt
Server-Key im Header x-querrio-key. Browser-Keys sind auf die erlaubten Origins beschränkt.
curl -X POST https://cdn.querrio.com/api/public/v1/search \
-H 'content-type: application/json' \
-H 'x-querrio-key: sk_dein_server_key' \
-d '{ "query": "beispiel suchanfrage", "limit": 10 }'Auto-Suggest
GET /api/public/v1/suggest?q= liefert Vorschläge aus erfolgreichen früheren Suchen und aus Titeln und Kategorien des Index — also auch direkt nach dem ersten Crawl. Aktivierung, Mindestzeichen, Anzahl und Direkttreffer stellst du im Backend unter „API & Widget“ ein; ist Auto-Suggest aus, antwortet die API mit enabled: false und leeren Listen.
// Typeahead: Vorschläge während der Eingabe
const asky = querrio.createClient({ key: "pk_dein_browser_key" });
input.addEventListener("input", async () => {
const res = await asky.suggest(input.value); // ältere Anfragen brechen automatisch ab
if (!res.enabled) return; // im Backend deaktiviert
render(res.items); // [{ text: "beispielbegriff", type: "popular" | "content" }]
render(res.hits); // Direkttreffer: id, url, title, category, image_url
showDate(res.date_label); // erkannter Zeitbezug, z. B. "morgen"
});
// Klick auf einen Vorschlag melden (fließt in Analytics und Autopilot):
asky.trackSuggestClick(item.text);
asky.trackSuggestClick(hit.title, hit.id);Antwortstruktur
{
"query": "beispiel suchanfrage",
"intent": { "topic": "beispielthema", "audience": null },
"date_from": null, "date_to": null, "date_label": null,
"top_hits": [
{ "id": "…", "url": "https://…", "title": "Beispiel-Seitentitel",
"snippet": "…", "image_url": "https://…", "category": "article",
"tags": ["beispiel"], "score": 0.91, "tier": "top",
"is_event": false, "event_start": null,
"event_location": null }
],
"results": [ … ],
"groups": [ { "category": "article", "label": "Beiträge", "count": 6, "hit_ids": [ … ] } ],
"suggestions": ["verwandter suchbegriff", "weiterer suchbegriff"],
"total": 7, "took_ms": 240, "query_id": "…"
}Debug-Mode
"debug": true (oder ?debug=1) liefert zusätzlich ein debug-Objekt: erkannter Intent, aufgelöstes Datum und Zielgruppe samt Quelle, aktive Filter und Ranking-Gewichte, alle Kandidaten vor dem Reranking, die Reihenfolge davor/danach, Zero-Result-Rescue, Teaser-Schwellen, Score-Details je Treffer und Timings. Server-Keys dürfen das immer; für Browser-Keys musst du es im Backend unter „API & Widget“ freischalten (sonst antwortet die API mit dem Header x-asky-debug: denied und ohne Debug-Objekt).
curl -X POST https://cdn.querrio.com/api/public/v1/search \
-H 'content-type: application/json' \
-H 'x-querrio-key: sk_dein_server_key' \
-d '{ "query": "beispiel suchanfrage", "debug": true }'"debug": {
"version": 1,
"query": { "raw": "beispiel suchanfrage",
"normalized": "beispiel suchanfrage",
"language": "de", "limit": 10, "candidate_limit": 30 },
"intent": { "topic": "beispielthema", "audience": null,
"entities": { … }, "prompt_override": false },
"resolved": { "date_from": null, "date_to": null,
"date_label": null, "date_source": "none",
"audience": null, "audience_source": "none",
"now": "2026-04-18T09:12:00Z" },
"filters": { "date_from": null, "date_to": null,
"audience": null, "category": null, "tags": [] },
"weights": { "vector": 0.6, "fts": 0.25, "quality": 0.1,
"date_boost": 0.15, "audience_boost": 0.1 },
"candidates": [ { "rank": 1, "id": "…", "url": "https://…",
"title": "Beispiel-Seitentitel", "score": 0.83,
"is_event": false, "event_start": null } ],
"rerank": { "applied": true, "model_used": true, "before": [ … ], "after": [ … ] },
"rescue": { "attempted": false, "from_cache": false, "candidates": [], "used": null },
"tiering": { "top_score": 0.91, "max_top_hits": 2,
"tier_threshold": 0.12, "top_hit_ids": [ … ] },
"scores": [ { "id": "…", "position": 1, "final_score": 0.91,
"relative_to_top": 1, "tier": "top", "category": "article" } ],
"timings": { "config_ms": 12, "intent_embed_ms": 180, "retrieval_ms": 41,
"rerank_ms": 260, "total_ms": 512 },
"notes": []
}Endpunkte
- POST
/api/public/v1/searchIntent-Suche. Liefert Top-Treffer, Liste, Kategorien und Vorschläge. - GET
/api/public/v1/suggest?q=Typeahead: Vorschläge und Instant-Treffer. - GET
/api/public/v1/schemaKategorien, Sprachen und Widget-Konfiguration. - POST
/api/public/v1/trackKlicks, Verweildauer und Feedback melden. - GET
/api/public/v1/openapi.jsonOpenAPI-3.1-Spezifikation für Codegen.
Limits & Hinweise
- Rate-Limit pro Key und Minute; bei Überschreitung antwortet die API mit 429.
- Feedback- und Klick-Events fließen direkt in das automatische Ranking-Training ein.
- Alle Endpunkte sind CORS-fähig und benötigen keinen Proxy.