Erreurs & Limites de débit
Codes d'erreur, codes de statut HTTP, stratégies de nouvelle tentative et en-têtes de limite de débit pour l'API publique AACsearch.
AACsearch mappe toutes les erreurs en amont vers des codes d'erreur typés avant de les retourner aux appelants. Les messages d'erreur bruts de AACSearch ne sont jamais transmis — cela empêche les fuites d'information et fournit un contrat d'erreur stable.
Format des réponses d'erreur
Toutes les réponses d'erreur suivent ce format :
{
"error": "error_code",
"message": "Description lisible par l'humain de l'erreur"
}Le champ error est lisible par machine et stable. Le champ message est destiné à la consommation humaine
et peut changer entre les versions.
Codes de statut HTTP et codes d'erreur
4xx — Erreurs client (ne pas réessayer automatiquement)
| HTTP | Code error | Cause |
|---|---|---|
| 400 | invalid_request | JSON malformé ou champ requis manquant |
| 400 | invalid_filter | Syntaxe d'expression filterBy invalide |
| 400 | invalid_sort | Expression sortBy invalide |
| 401 | unauthorized | Clé API ou token limité manquant, invalide ou expiré |
| 401 | token_expired | La durée de vie du token limité est dépassée |
| 403 | forbidden | La clé n'a pas la portée requise (search ou connector_write) |
| 403 | origin_not_allowed | L'origine de la requête n'est pas dans allowedOrigins pour la clé |
| 404 | index_not_found | L'indexSlug spécifié n'existe pas pour cette organisation |
| 429 | rate_limit_exceeded | Limite de débit par clé dépassée |
| 429 | quota_exceeded | Le quota mensuel d'unités de recherche de l'organisation est épuisé |
5xx — Erreurs serveur (possibilité de nouvelle tentative avec backoff)
| HTTP | Code error | Cause |
|---|---|---|
| 502 | search_failed | Erreur AACSearch en amont (cluster inaccessible, timeout de requête) |
| 502 | ingest_failed | Échec de la mise en file d'attente des documents dans le buffer d'ingestion |
| 503 | service_unavailable | L'API AACsearch est temporairement indisponible |
Limitation de débit
La limitation de débit est appliquée par clé API en utilisant un compteur à fenêtre glissante.
Limite par défaut : 60 requêtes par minute par clé.
Lorsque la limite est dépassée, la réponse est :
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 of 60 req/min exceeded. Retry after 15 seconds."
}En-têtes de réponse
Ces en-têtes sont inclus dans toutes les réponses réussies et les réponses de limitation de débit :
| En-tête | Description |
|---|---|
X-RateLimit-Limit | Total des requêtes autorisées par fenêtre |
X-RateLimit-Remaining | Requêtes restantes dans la fenêtre actuelle |
X-RateLimit-Reset | Horodatage Unix lorsque la fenêtre se réinitialise |
Retry-After | Secondes à attendre avant de réessayer (uniquement sur 429) |
Épuisement du quota
Lorsque le quota mensuel du plan est dépassé :
HTTP/1.1 429 Too Many Requests
{
"error": "quota_exceeded",
"message": "Monthly search-unit quota exhausted. Upgrade your plan or wait for reset."
}Les unités de recherche se réinitialisent le 1er de chaque mois calendaire. Mettez à niveau le plan dans Settings → Billing pour restaurer immédiatement le quota.
Stratégie de nouvelle tentative pour les clients
| Classe d'erreur | Action |
|---|---|
| 4xx (sauf 429) | Ne pas réessayer — corriger la requête |
429 rate_limit_exceeded | Attendre les secondes Retry-After, puis réessayer |
429 quota_exceeded | Ne pas réessayer jusqu'à la réinitialisation du quota ou la mise à niveau du plan |
502 search_failed | Réessayer une fois après 1 seconde ; si cela échoue à nouveau, afficher l'erreur à l'utilisateur |
| 503 | Réessayer avec backoff exponentiel : 1s → 2s → 4s → abandonner |
| Erreur réseau | Réessayer avec backoff exponentiel |
Comportement de nouvelle tentative du SDK
Le SDK @repo/search-client ne réessaie pas automatiquement les erreurs 5xx. Implémentez la logique de nouvelle tentative
dans votre couche applicative ou utilisez une bibliothèque comme 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, // réessayer uniquement les erreurs serveur
});Comportement de nouvelle tentative du module CMS
Le buffer d'ingestion AACsearch est conçu pour les nouvelles tentatives :
- Les requêtes de synchronisation du connecteur (
sync/full,sync/delta) mettent les documents en file d'attente dansSearchIngestBuffer - Le worker en arrière-plan réessaie les lignes ayant échoué avec un backoff exponentiel
- Le module CMS n'a pas besoin d'implémenter la logique de nouvelle tentative pour les documents individuels — le buffer s'en charge
- Le module CMS doit réessayer la requête HTTP elle-même sur les réponses 5xx (pas sur les réponses 4xx)
Débogage en développement
Pour les erreurs 502 en développement :
- Vérifiez si AACSearch est en cours d'exécution :
curl http://localhost:8108/health - Vérifiez les logs de l'API pour l'erreur AACSearch originale (mappée avant de répondre au client)
- Vérifiez
AACSEARCH_HOST,AACSEARCH_PORT,AACSEARCH_API_KEYdans.env.local
Pour les erreurs 401 :
- Vérifiez que la clé n'a pas expiré
- Vérifiez que le préfixe de la clé correspond à l'opération (
ss_search_*pour la recherche,ss_connector_*pour l'ingestion) - Pour les tokens limités : vérifiez le champ
expiresAtdans la charge utile décodée
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.