Fehler & Rate-Limits
Fehlercodes, HTTP-Statuscodes, Retry-Strategien und Rate-Limit-Header für die öffentliche AACsearch-API.
AACsearch bildet alle vorgelagerten Fehler auf typisierte Fehlercodes ab, bevor sie an Aufrufer zurückgegeben werden. Rohe Suchmaschinenfehlermeldungen werden niemals weitergeleitet — dies verhindert Informationslecks und bietet einen stabilen Fehlervertrag.
Fehlerantwortformat
Alle Fehlerantworten folgen diesem Format:
{
"error": "error_code",
"message": "Für Menschen lesbare Beschreibung des Fehlers"
}Das error-Feld ist maschinenlesbar und stabil. Das message-Feld ist für den menschlichen Konsum
gedacht und kann sich zwischen Versionen ändern.
HTTP-Statuscodes und Fehlercodes
4xx — Client-Fehler (nicht automatisch wiederholen)
| HTTP | error-Code | Ursache |
|---|---|---|
| 400 | invalid_request | Fehlerhaftes JSON oder fehlendes Pflichtfeld |
| 400 | invalid_filter | Ungültige filterBy-Ausdruckssyntax |
| 400 | invalid_sort | Ungültiger sortBy-Ausdruck |
| 401 | unauthorized | Fehlender, ungültiger oder abgelaufener API-Schlüssel oder Token |
| 401 | token_expired | TTL des eingeschränkten Tokens ist abgelaufen |
| 403 | forbidden | Schlüssel hat nicht den erforderlichen Bereich (search oder connector_write) |
| 403 | origin_not_allowed | Anfrage-Ursprung nicht in allowedOrigins für den Schlüssel |
| 404 | index_not_found | Der angegebene indexSlug existiert für diese Organisation nicht |
| 429 | rate_limit_exceeded | Rate-Limit pro Schlüssel überschritten |
| 429 | quota_exceeded | Monatliches Sucheinheiten-Kontingent für die Organisation erschöpft |
5xx — Server-Fehler (kann mit Backoff wiederholt werden)
| HTTP | error-Code | Ursache |
|---|---|---|
| 502 | search_failed | Vorgelagerter Suchmaschinenfehler (Cluster nicht erreichbar, Abfrage-Timeout) |
| 502 | ingest_failed | Dokumente konnten nicht in den Ingest-Buffer eingereiht werden |
| 503 | service_unavailable | AACsearch-API ist vorübergehend nicht verfügbar |
Rate-Limiting
Rate-Limiting wird pro API-Schlüssel über einen Gleitfenster-Zähler durchgesetzt.
Standard-Limit: 60 Anfragen pro Minute pro Schlüssel.
Wenn das Limit überschritten wird, ist die Antwort:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 15
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1717201234
{
"error": "rate_limit_exceeded",
"message": "Rate-Limit von 60 Req/Min überschritten. Bitte nach 15 Sekunden erneut versuchen."
}Antwortheader
Diese Header sind bei allen erfolgreichen und rate-limitierten Antworten enthalten:
| Header | Beschreibung |
|---|---|
X-RateLimit-Limit | Gesamtanfragen pro Fenster erlaubt |
X-RateLimit-Remaining | Verbleibende Anfragen im aktuellen Fenster |
X-RateLimit-Reset | Unix-Zeitstempel, wenn das Fenster zurückgesetzt wird |
Retry-After | Sekunden bis zum nächsten Versuch (nur bei 429) |
Kontingenterschöpfung
Wenn das monatliche Plan-Kontingent überschritten wird:
HTTP/1.1 429 Too Many Requests
{
"error": "quota_exceeded",
"message": "Monatliches Sucheinheiten-Kontingent erschöpft. Upgraden Sie Ihren Plan oder warten Sie auf die Rücksetzung."
}Sucheinheiten werden am 1. eines jeden Kalendermonats zurückgesetzt. Upgraden Sie den Plan unter Einstellungen → Abrechnung, um das Kontingent sofort wiederherzustellen.
Retry-Strategie für Clients
| Fehlerklasse | Aktion |
|---|---|
| 4xx (außer 429) | Nicht wiederholen — die Anfrage korrigieren |
429 rate_limit_exceeded | Retry-After-Sekunden warten, dann erneut versuchen |
429 quota_exceeded | Nicht wiederholen, bis Kontingent zurückgesetzt oder Plan upgraded |
502 search_failed | Einmal nach 1 Sekunde wiederholen; wenn erneut fehlschlägt, Fehler anzeigen |
| 503 | Exponentielles Backoff: 1s → 2s → 4s → aufgeben |
| Netzwerkfehler | Exponentielles Backoff |
SDK-Retry-Verhalten
Das @repo/search-client-SDK wiederholt bei 5xx-Fehlern nicht automatisch. Implementieren Sie Retry-
Logik in Ihrer Anwendungsschicht oder verwenden Sie eine Bibliothek wie p-retry.
import pRetry from "p-retry";
const results = await pRetry(() => client.search({ q: "headphones" }), {
retries: 3,
factor: 2,
minTimeout: 1000,
shouldRetry: (err) => err.status >= 500, // nur Server-Fehler wiederholen
});CMS-Modul-Retry-Verhalten
Der AACsearch-Ingest-Buffer ist für Retry ausgelegt:
- Connector-Sync-Anfragen (
sync/full,sync/delta) reihen Dokumente inSearchIngestBufferein - Der Hintergrund-Worker wiederholt fehlgeschlagene Zeilen mit exponentiellem Backoff
- Das CMS-Modul muss kein Retry für einzelne Dokumente implementieren — der Buffer behandelt es
- Das CMS-Modul sollte die HTTP-Anfrage selbst bei 5xx-Antworten wiederholen (nicht bei 4xx)
Entwicklungs-Debugging
Für 502-Fehler während der Entwicklung:
- Prüfen, ob AACSearch läuft:
curl http://localhost:8108/health - API-Protokolle auf den ursprünglichen Suchmaschinenfehler prüfen (abgebildet vor der Antwort an den Client)
AACSEARCH_HOST,AACSEARCH_PORT,AACSEARCH_API_KEYin.env.localprüfen
Für 401-Fehler:
- Sicherstellen, dass der Schlüssel nicht abgelaufen ist
- Sicherstellen, dass das Schlüsselpräfix zur Operation passt (
ss_search_*für Suche,ss_connector_*für Ingest) - Für eingeschränkte Token:
expiresAt-Feld im dekodierten Payload prüfen
Request ID and trace ID
Every response includes two correlation identifiers — pass them to support so we can find the request in logs without back-and-forth.
| Header | Body field | Where it comes from |
|---|---|---|
X-Request-Id | requestId | Generated per-request by the API edge. Always present. |
X-Trace-Id | traceId (only present when distributed tracing is enabled) | OpenTelemetry trace ID, propagated through worker → AACSearch → response |
Capture them client-side from the SDK:
import { SearchClient } from "@aacsearch/client";
try {
const result = await client.search({ q: "shoes" });
} catch (err) {
if (err instanceof AacSearchError) {
console.error("aacsearch error", {
code: err.code,
requestId: err.requestId, // for support tickets
traceId: err.traceId, // for distributed tracing dashboards
});
}
}Server-side handlers should log them on every request, success or failure:
const result = await client.search(opts);
logger.info({
event: "search.ok",
requestId: result.requestId,
searchTimeMs: result.searchTimeMs,
});SDK error class
The TypeScript SDK exposes a typed AacSearchError that wraps every non-2xx response:
import { AacSearchError } from "@aacsearch/client";
try {
await client.search({ q: "shoes" });
} catch (err) {
if (err instanceof AacSearchError) {
err.code; // string — error code from the body, stable
err.message; // string — human-readable
err.status; // number — HTTP status (0 for network errors)
err.retryable; // boolean — derived from body, fall back to status >= 500
err.requestId; // string | undefined
err.traceId; // string | undefined
err.docsUrl; // string | undefined
err.response; // Response | undefined — raw fetch Response if available
}
}Same shape in Python (SdkError) and PHP (AacSearchException).
UI state recipes
Concrete React components for the four most-common error states. All are headless — drop them into your design system.
Invalid key
function SearchInvalidKey({ requestId }: { requestId?: string }) {
return (
<div role="alert" className="search-error">
<h3>Search is not configured correctly</h3>
<p>Your API key is invalid, expired, or has been revoked.</p>
{requestId && <small>Reference: {requestId}</small>}
</div>
);
}Quota exceeded
function SearchQuotaExceeded({ resetDate }: { resetDate: Date }) {
return (
<div role="alert" className="search-error">
<h3>Search is temporarily unavailable</h3>
<p>The site has reached its monthly search limit.</p>
<p>Service will resume on {resetDate.toLocaleDateString()}.</p>
</div>
);
}No results
function NoResults({ query }: { query: string }) {
return (
<div className="no-results">
<h3>No results for "{query}"</h3>
<p>Try a shorter query, different keywords, or browse our categories below.</p>
</div>
);
}For a richer no-results UI with did-you-mean, popular categories, and bestseller fallback, see the no-results recipe.
Server error / network
function SearchUnavailable({ requestId, onRetry }: { requestId?: string; onRetry: () => void }) {
return (
<div role="alert" className="search-error">
<h3>Search is temporarily unavailable</h3>
<p>We could not reach the search service. Please try again in a moment.</p>
<button onClick={onRetry}>Retry</button>
{requestId && <small>Reference: {requestId}</small>}
</div>
);
}For the full troubleshooting flow per error code, see the troubleshooting hub.
Related
- Troubleshooting hub — symptom → fix decision trees
- Auth errors — 401/403 deep dive
- Rate limits & quota — 429 deep dive
- Server-side helpers — production retry patterns
- No-results recommendations — richer empty-state UX
Search Core Relevance
Query processing, queryBy weights, typo tolerance, synonyms, curations, ranking — the developer-side reference for how the search engine decides which documents to return.
Reindexing & Zero-Downtime
How the alias-swap reindex strategy works, when to trigger it, and how to monitor progress.