TypeScript SDK
Der generierte Client @repo/sdk — Konstruktoroptionen, alle Methoden, Retry-Verhalten und der Typ AACSearchError.
@repo/sdk ist ein typisierter Client, generiert aus packages/api/routes/v1/openapi.json (neu bauen mit pnpm sdk:generate). Es ist ein ESM-Paket auf Basis des globalen fetch und läuft damit in Node 18+, Bun, Deno und Edge-Runtimes.
import { AACSearchClient, AACSearchError } from "@repo/sdk";
const client = new AACSearchClient({
apiKey: process.env.AAC_KEY!,
baseUrl: "https://search.example.com/api/v1",
});
const result = await client.search("documentation", { q: "preise", per_page: 5 });
console.log(result.found, result.hits[0]?.document.title);Konstruktor
interface AACSearchClientOptions {
/** aac_live_… Schlüssel. Pflicht. */
apiKey: string;
/** Standard: http://localhost:3000/api/v1 */
baseUrl?: string;
/** Wiederholungen für 429 und 5xx. Standard 2. */
maxRetries?: number;
/** Eigene fetch-Implementierung, z. B. für Tracing oder einen Proxy. */
fetch?: typeof fetch;
}Ein leerer apiKey wirft sofort, vor jedem Request:
new AACSearchClient({ apiKey: "" });
// AACSearchError: apiKey is required (status 401, code MISSING_API_KEY)Ein abschließender Slash in baseUrl wird entfernt, https://host/api/v1/ und https://host/api/v1 sind also gleichwertig.
Fehler und Retries
Jeder Fehler ist ein AACSearchError mit dem geparsten Umschlag und dem HTTP-Status:
class AACSearchError extends Error {
name: "AACSearchError";
status: number; // 401, 403, 422, 429, 502 …
code: string; // UNAUTHORIZED, QUOTA_EXCEEDED, VALIDATION_ERROR …
details?: unknown; // details aus dem Fehler-Body
requestId?: string; // im Supportfall angeben
}try {
await client.upsertDocuments("documentation", {
documents: [{ external_id: "pricing", content: "Pro kostet 29 USD im Monat." }],
action: "create",
});
} catch (error) {
if (error instanceof AACSearchError) {
if (error.code === "QUOTA_EXCEEDED") {
console.error(
"Kontingent erschöpft, weiter ab",
(error.details as { resetsAt: string }).resetsAt,
);
} else if (error.status === 429) {
// Rate-Limit — das SDK hat maxRetries bereits ausgeschöpft
} else {
console.error(error.code, error.message, error.requestId);
}
}
throw error;
}Retry-Verhalten: 429 und alle 5xx werden bis zu maxRetries mal wiederholt (Standard 2) mit exponentiellem Backoff von 250 ms, 500 ms, 1 s. Andere 4xx werden nie wiederholt. Sind die Versuche erschöpft, wird die letzte Antwort geparst und geworfen. Fehlt im Body ein error-Objekt, wird code zu REQUEST_FAILED und die Meldung zu Request failed with <status>.
Timeouts erzwingt der Client nicht — übergeben Sie ein eigenes fetch mit AbortSignal, wenn Sie eines brauchen.
Methoden
Abfragen und Bodies sind einfache Objekte; der Client serialisiert nur definierte, nicht-leere Werte, undefined und "" werden also weggelassen statt als leere Parameter gesendet.
Suche und Discovery
| Methode | HTTP |
|---|---|
search(index, query) | GET /indexes/{index}/search |
searchPost(index, body) | POST /indexes/{index}/search |
suggest(index, query) | GET /indexes/{index}/suggest |
multiSearch(body) | POST /indexes/multi_search |
nlSearch(index, body) | POST /indexes/{index}/nl-search |
indexStats(index, query?) | GET /indexes/{index}/stats |
health() | GET /health |
capabilities() | GET /capabilities |
openapi() | GET /openapi.json |
metrics() | GET /metrics |
const suggestions = await client.suggest("documentation", { q: "pre", limit: 5 });
const stats = await client.indexStats("documentation");
const caps = await client.capabilities();
if (!caps.features.autoEmbedding)
throw new Error("semantische Suche auf diesem Knoten nicht verfügbar");Dokumente
| Methode | HTTP |
|---|---|
upsertDocuments(index, body) | POST /indexes/{index}/documents |
getDocument(index, externalId) | GET /indexes/{index}/documents/{externalId} |
deleteDocument(index, externalId) | DELETE /indexes/{index}/documents/{externalId} |
deleteDocuments(index, query) | DELETE /indexes/{index}/documents?filter_by=… |
bulkUpdateDocuments(index, body) | POST /indexes/{index}/documents/bulk |
exportDocuments(index) | GET /indexes/{index}/documents/export |
await client.upsertDocuments("documentation", {
documents: [
{
external_id: "pricing",
title: "Preise",
content: "Pro kostet 29 USD im Monat und enthält 500.000 Suchen.",
tags: ["billing"],
},
],
action: "upsert",
});
const doc = await client.getDocument("documentation", "pricing");
await client.deleteDocument("documentation", "pricing");
await client.deleteDocuments("documentation", { filter_by: "source_type:=connector" });KI
| Methode | HTTP |
|---|---|
answer(index, body) | POST /indexes/{index}/answer — RAG-Antwort mit Quellen |
assistantAsk(index, body) | POST /indexes/{index}/assistant — Dialogzug |
assistantStream(index, body, onEvent) | POST /indexes/{index}/assistant/stream — SSE |
assistantNative(index, body) | POST /indexes/{index}/assistant/native — Dialogmodell der Engine |
assistantThreads(index) | GET /indexes/{index}/assistant |
assistantThread(index, conversationId) | GET /indexes/{index}/assistant/{conversationId} |
conversationModels(index) | GET /indexes/{index}/conversation-models |
voiceSearch(index, body) | POST /indexes/{index}/voice |
imageSearch(index, body) | POST /indexes/{index}/image |
const answer = await client.answer("documentation", {
question: "Was kostet der Pro-Tarif?",
per_page: 4,
});
console.log(answer.answer, answer.citations);
await client.assistantStream("documentation", { question: "Und die Limits?" }, (event) => {
if (event.type === "delta")
process.stdout.write(String((event.payload as { text?: string }).text ?? ""));
});assistantStream liest den Response-Body selbst, trennt Frames an einer Leerzeile, parst jede data: -Zeile und übergibt { type, payload }. Event-Typen sind citations, delta, done und error. Das Promise löst sich beim Schließen des Streams; ein leerer Body wirft AACSearchError mit Code SSE_EMPTY.
Relevanz und Betrieb
| Methode | HTTP |
|---|---|
listSynonyms(index) / putSynonym(index, body) / deleteSynonym(index, id) | /indexes/{index}/synonyms |
listCurations(index) / putCuration(index, body) / deleteCuration(index, id) | /indexes/{index}/curations |
trackEvent(index, body) | POST /indexes/{index}/events |
reindex(index) | POST /indexes/{index}/reindex |
scopedKey(index, body) | POST /indexes/{index}/scoped-key |
triggerSnapshot() | POST /admin/snapshot |
runJobs(query) | POST /admin/jobs/run |
exportOrganization() | GET /organization/export |
purgeOrganization(query) | DELETE /organization/data |
Analytics und Abrechnung
| Methode | HTTP |
|---|---|
analyticsOverview(query) | GET /analytics/overview |
analyticsOverviewCsv(query) | GET /analytics/overview.csv |
slowQueries(query) | GET /analytics/slow-queries |
trendingQueries(query) | GET /analytics/trending |
usage() | GET /billing/usage |
wallet() | GET /billing/wallet |
const { overview } = await client.analyticsOverview({ days: 7 });
console.log(overview.totals.searches, overview.totals.zeroResultRate);
const { usage } = await client.usage();
console.log(`${usage.searches.used}/${usage.searches.limit} Suchen in dieser Periode`);Bekannte Lücken des generierten Clients
Der Client wird mechanisch aus dem OpenAPI-Dokument erzeugt, und drei Methoden passen nicht exakt zum Server. Sie stehen hier, damit Sie keinen Nachmittag damit verlieren:
| Methode | Problem | Workaround |
|---|---|---|
multiSuggest() | Sendet keinen Body, obwohl der Endpunkt { indexes, q } verlangt | fetch auf POST /indexes/suggest/multi |
exportDocuments(index) | Parst den NDJSON-Export mit response.json() | Export per fetch holen und Text lesen |
webhookIngest(index) | Sendet weder Body noch Signatur-Header | Selbst signieren und senden, siehe Webhooks |
Alles andere entspricht eins zu eins einem dokumentierten Endpunkt.