AACsearch
API de recherche

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)

HTTPCode errorCause
400invalid_requestJSON malformé ou champ requis manquant
400invalid_filterSyntaxe d'expression filterBy invalide
400invalid_sortExpression sortBy invalide
401unauthorizedClé API ou token limité manquant, invalide ou expiré
401token_expiredLa durée de vie du token limité est dépassée
403forbiddenLa clé n'a pas la portée requise (search ou connector_write)
403origin_not_allowedL'origine de la requête n'est pas dans allowedOrigins pour la clé
404index_not_foundL'indexSlug spécifié n'existe pas pour cette organisation
429rate_limit_exceededLimite de débit par clé dépassée
429quota_exceededLe quota mensuel d'unités de recherche de l'organisation est épuisé

5xx — Erreurs serveur (possibilité de nouvelle tentative avec backoff)

HTTPCode errorCause
502search_failedErreur AACSearch en amont (cluster inaccessible, timeout de requête)
502ingest_failedÉchec de la mise en file d'attente des documents dans le buffer d'ingestion
503service_unavailableL'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êteDescription
X-RateLimit-LimitTotal des requêtes autorisées par fenêtre
X-RateLimit-RemainingRequêtes restantes dans la fenêtre actuelle
X-RateLimit-ResetHorodatage Unix lorsque la fenêtre se réinitialise
Retry-AfterSecondes à 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'erreurAction
4xx (sauf 429)Ne pas réessayer — corriger la requête
429 rate_limit_exceededAttendre les secondes Retry-After, puis réessayer
429 quota_exceededNe pas réessayer jusqu'à la réinitialisation du quota ou la mise à niveau du plan
502 search_failedRéessayer une fois après 1 seconde ; si cela échoue à nouveau, afficher l'erreur à l'utilisateur
503Réessayer avec backoff exponentiel : 1s → 2s → 4s → abandonner
Erreur réseauRé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 dans SearchIngestBuffer
  • 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 :

  1. Vérifiez si AACSearch est en cours d'exécution : curl http://localhost:8108/health
  2. Vérifiez les logs de l'API pour l'erreur AACSearch originale (mappée avant de répondre au client)
  3. Vérifiez AACSEARCH_HOST, AACSEARCH_PORT, AACSEARCH_API_KEY dans .env.local

Pour les erreurs 401 :

  1. Vérifiez que la clé n'a pas expiré
  2. Vérifiez que le préfixe de la clé correspond à l'opération (ss_search_* pour la recherche, ss_connector_* pour l'ingestion)
  3. Pour les tokens limités : vérifiez le champ expiresAt dans 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.

HeaderBody fieldWhere it comes from
X-Request-IdrequestIdGenerated per-request by the API edge. Always present.
X-Trace-IdtraceId (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 &quot;{query}&quot;</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.

On this page