Errores y Límites de Velocidad
Códigos de error, códigos de estado HTTP, estrategias de reintento y encabezados de límite de velocidad para la API pública de AACsearch.
AACsearch mapea todos los errores upstream a códigos de error tipados antes de devolverlos a los llamadores. Los mensajes de error crudos de AACSearch nunca se reenvían — esto evita la fuga de información y proporciona un contrato de errores estable.
Formato de respuesta de error
Todas las respuestas de error siguen este formato:
{
"error": "error_code",
"message": "Descripción legible por humanos del error"
}El campo error es legible por máquina y estable. El campo message es para consumo humano
y puede cambiar entre versiones.
Códigos de estado HTTP y códigos de error
4xx — Errores del cliente (no reintentar automáticamente)
| HTTP | Código error | Causa |
|---|---|---|
| 400 | invalid_request | JSON malformado o campo requerido faltante |
| 400 | invalid_filter | Sintaxis de expresión filterBy inválida |
| 400 | invalid_sort | Expresión sortBy inválida |
| 401 | unauthorized | API key o token con ámbito faltante, inválido o expirado |
| 401 | token_expired | El TTL del token con ámbito ha expirado |
| 403 | forbidden | La clave no tiene el ámbito requerido (search o connector_write) |
| 403 | origin_not_allowed | El origen de la solicitud no está en allowedOrigins para la clave |
| 404 | index_not_found | El indexSlug especificado no existe para esta organización |
| 429 | rate_limit_exceeded | Límite de velocidad por clave superado |
| 429 | quota_exceeded | La cuota mensual de unidades de búsqueda de la organización está agotada |
5xx — Errores del servidor (puede reintentar con retroceso)
| HTTP | Código error | Causa |
|---|---|---|
| 502 | search_failed | Error upstream de AACSearch (clúster inalcanzable, tiempo de espera de consulta) |
| 502 | ingest_failed | No se pudieron poner en cola los documentos en el buffer de ingestión |
| 503 | service_unavailable | La API de AACsearch no está disponible temporalmente |
Límite de velocidad
El límite de velocidad se aplica por API key usando un contador de ventana deslizante.
Límite predeterminado: 60 solicitudes por minuto por clave.
Cuando se supera el límite, la respuesta es:
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": "Límite de velocidad de 60 req/min superado. Reintente después de 15 segundos."
}Encabezados de respuesta
Estos encabezados se incluyen en todas las respuestas exitosas y con límite de velocidad:
| Encabezado | Descripción |
|---|---|
X-RateLimit-Limit | Total de solicitudes permitidas por ventana |
X-RateLimit-Remaining | Solicitudes restantes en la ventana actual |
X-RateLimit-Reset | Marca de tiempo Unix cuando se resetea la ventana |
Retry-After | Segundos a esperar antes de reintentar (solo en 429) |
Agotamiento de cuota
Cuando se agota la cuota del plan mensual:
HTTP/1.1 429 Too Many Requests
{
"error": "quota_exceeded",
"message": "Cuota mensual de unidades de búsqueda agotada. Actualice su plan o espere el reseteo."
}Las unidades de búsqueda se resetean el día 1 de cada mes calendario. Actualice el plan en Configuración → Facturación para restaurar la cuota inmediatamente.
Estrategia de reintento para clientes
| Clase de error | Acción |
|---|---|
| 4xx (excepto 429) | No reintentar — corrija la solicitud |
429 rate_limit_exceeded | Espere los segundos de Retry-After, luego reintente |
429 quota_exceeded | No reintentar hasta que se resetee la cuota o se actualice el plan |
502 search_failed | Reintente una vez después de 1 segundo; si falla nuevamente, muestre error al usuario |
| 503 | Reintente con retroceso exponencial: 1s → 2s → 4s → abandone |
| Error de red | Reintente con retroceso exponencial |
Comportamiento de reintento del SDK
El SDK @repo/search-client no reintenta automáticamente en errores 5xx. Implemente la lógica de reintento
en la capa de su aplicación o use una biblioteca como p-retry.
import pRetry from "p-retry";
const results = await pRetry(() => client.search({ q: "auriculares" }), {
retries: 3,
factor: 2,
minTimeout: 1000,
shouldRetry: (err) => err.status >= 500, // solo reintentar errores del servidor
});Comportamiento de reintento del módulo CMS
El buffer de ingestión de AACsearch está diseñado para reintentos:
- Las solicitudes de sincronización del conector (
sync/full,sync/delta) ponen en cola los documentos enSearchIngestBuffer - El trabajador en segundo plano reintenta las filas con error con retroceso exponencial
- El módulo CMS no necesita implementar reintento para documentos individuales — el buffer lo maneja
- El módulo CMS debe reintentar la propia solicitud HTTP en respuestas 5xx (no en 4xx)
Depuración en desarrollo
Para errores 502 durante el desarrollo:
- Verifique si AACSearch está en ejecución:
curl http://localhost:8108/health - Verifique los registros de la API para el error original de AACSearch (mapeado antes de responder al cliente)
- Verifique
AACSEARCH_HOST,AACSEARCH_PORT,AACSEARCH_API_KEYen.env.local
Para errores 401:
- Verifique que la clave no haya expirado
- Verifique que el prefijo de la clave coincida con la operación (
ss_search_*para búsqueda,ss_connector_*para ingestión) - Para tokens con ámbito: verifique el campo
expiresAten el payload decodificado
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.