Fehler und Limits
Der Fehler-Umschlag, jeder Code mit Status, Rate-Limit-Semantik, Tarifkontingente und das Verhalten des Wallets bei Überschreitung.
Der Fehler-Umschlag
Jeder Fehler — Validierung, Auth, Kontingent, Engine — nutzt dieselbe Form:
{
"error": {
"code": "QUOTA_EXCEEDED",
"message": "Monthly searches quota exhausted (500000/500000 for 2026-10)",
"request_id": "req_3c5e7a9b1d3f5a7c9e1b3d5f",
"details": {
"metric": "searches",
"limit": 500000,
"used": 500000,
"period": "2026-10",
"resetsAt": "2026-11-01T00:00:00.000Z"
}
}
}code ist ein stabiler Bezeichner, auf den Sie verzweigen können; message ist für Menschen und darf sich ändern; request_id kommt auch im Header x-request-id. details gibt es nur, wenn es etwas Nützliches zu sagen gibt.
curl -sS -i "$AAC_API/indexes/documentation/search?q=preise&num_typos=9" \
-H "Authorization: Bearer $AAC_KEY"{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request payload is invalid",
"request_id": "req_7d9f1b3c5e7a9b1d3f5c7a9b",
"details": {
"issues": [
{
"code": "too_big",
"path": ["num_typos"],
"message": "Number must be less than or equal to 3"
}
]
}
}
}Codes nach Status
400
| Code | Auslöser |
|---|---|
INVALID_QUERY | Fehlerhafte Filter-, Sortier-, Feldlisten- oder vector_query-Syntax; organization_id in filter_by; fehlendes filter_by beim Massenlöschen; Index ohne Auto-Embedding-Feld bei semantic=true; mehr als 20 Suchen in multi_search |
FIELD_NOT_IN_SCHEMA | filter_by, sort_by, facet_by, query_by oder group_by nennt ein Feld, das im Index-Schema nicht deklariert ist (Dokumente können es trotzdem enthalten). details.field ist das Feld aus Ihrer Anfrage, details.param der Parameter. Feld unter Schema hinzufügen und neu indexieren |
INVALID_JSON | Webhook-Body, der kein JSON ist |
PRIVATE_FIELD | query_by, filter_by, sort_by, facet_by, include_fields oder group_by nennt ein privates Feld, dem Schlüssel fehlt documents:read_private (auch /embed/search) |
PRIVATE_FIELDS_PRESENT | POST /indexes/{slug}/scoped-key für einen Index mit privaten Feldern durch einen Schlüssel ohne documents:read_private. details.fields |
401
| Code | Auslöser |
|---|---|
UNAUTHORIZED | Fehlender oder fehlerhafter Authorization-Header, unbekannter oder widerrufener Schlüssel, fehlende Signatur-Header beim Webhook |
KEY_ROTATED | Der Schlüssel wurde rotiert und die Kulanzfrist ist abgelaufen. Den bei der Rotation ausgegebenen Nachfolger verwenden |
EMBED_SESSION_REQUIRED / EMBED_SESSION_EXPIRED / EMBED_SESSION_INVALID | /embed/search, /embed/events, /embed/config, /embed/recommendations ohne, mit abgelaufenem oder mit ungültigem Embed-Session-Token. Neues Token mit POST /embed/session ausstellen |
EMBED_SESSION_REVOKED | Der API-Schlüssel oder das gespeicherte Widget, das die Embed-Session ausgestellt hat, wurde widerrufen, ist abgelaufen oder deaktiviert; das Token wird innerhalb von etwa 30 Sekunden ungültig |
STALE_SIGNATURE | Webhook-Zeitstempel außerhalb des 300-Sekunden-Fensters |
INVALID_SIGNATURE | HMAC des Webhooks passt nicht |
402
| Code | Auslöser |
|---|---|
PAYMENT_REQUIRED | Die Funktion ist im Tarif nicht enthalten (details.feature, details.upgradeTo), oder das Wallet deckt den nächsten Überschreitungsblock nicht (details.blockCents). Beides heißt „zahlen oder Tarif wechseln“, nicht „später erneut versuchen“ |
PLAN_LIMIT_REACHED | Ein weiterer Index oder API-Schlüssel bzw. ein Dokumentimport würde ein Tariflimit (Indizes, Schlüssel oder Dokumente) überschreiten |
FEATURE_NOT_AVAILABLE | Organisation suspended nach Zahlungsausfall: läuft als Free, bezahlte Funktionen werden mit details.reason = "past_due" abgelehnt — offene Rechnung bezahlen, nicht upgraden |
403
| Code | Auslöser |
|---|---|
FORBIDDEN | Dem Schlüssel fehlt der nötige Scope, oder er ist an einen anderen Index gebunden; Embed-Session für eine andere Operation als search:read verwendet |
ORIGIN_NOT_ALLOWED | Ein Request, dessen Origin-Header nicht in den allowedOrigins des Schlüssels steht. Wird serverseitig für jede schlüsselauthentifizierte Route (und POST /embed/session) erzwungen, nicht nur über CORS-Header; Requests ohne Origin-Header (Server-zu-Server) sind nicht betroffen |
INDEX_MISMATCH | Embed-Session ist auf einen anderen Index beschränkt als den angefragten |
KEY_IN_GRACE | Der aufrufende Schlüssel wurde rotiert und ist in seinem Übergangsfenster: Er kann weder Schlüssel rotieren noch eingeschränkte Schlüssel ausstellen. Den Nachfolger verwenden |
KEY_LIFETIME_EXCEEDS_CALLER | POST /api-keys/{keyId}/rotate auf einen Schlüssel, der den aufrufenden Schlüssel überdauern würde |
FORBIDDEN (mandantenweit) | GET /organization/export, DELETE /organization/data oder /webhooks/* mit einem an einen Index gebundenen Schlüssel |
Ein acl-Claim in einer Suchanfrage wird ignoriert — nicht abgelehnt —, wenn der Schlüssel eine allowedOrigins-Liste hat oder der Request einen Origin-Header trägt: Ein im Browser sichtbarer Schlüssel kann nicht wählen, wessen Dokumente er sieht. Solche Suchen sehen nur Dokumente ohne ACL.
404
| Code | Auslöser |
|---|---|
INDEX_NOT_FOUND | Unbekannter Index-Slug in der Organisation des Schlüssels; Embed-Session ohne auflösbaren Index. Ein „Collection nicht gefunden“ der Engine wird unter diesem öffentlichen Code gemeldet |
DOCUMENT_NOT_FOUND | GET/DELETE auf eine unbekannte external_id |
PROFILE_NOT_FOUND | ?profile= nennt ein Profil, das der Index nicht hat |
CONVERSATION_NOT_FOUND | Dialog-ID, die nicht zum Index gehört |
NOT_FOUND | /metrics ohne gültiges x-metrics-token |
JOB_NOT_FOUND | Job eines anderen Mandanten oder (indexgebundener Schlüssel) eines anderen Index |
SNAPSHOT_NOT_FOUND | Unbekannter Snapshot oder einer eines anderen Index/Mandanten |
409
| Code | Auslöser |
|---|---|
INDEX_ALREADY_EXISTS | Duplikat auf Engine-Ebene beim Anlegen einer Collection (Reindex, Konsole); der physische Collection-Name wird nie offengelegt |
CONFLICT | Konfliktierender Schreibvorgang, erkannt in der Service-Schicht |
IDEMPOTENCY_IN_FLIGHT | Ein Dokumentimport mit demselben Idempotency-Key läuft noch — nach dessen Ende erneut versuchen |
JOB_CONFLICT | Reindex oder Restore angefordert (auch mit ?sync=true), während ein Reindex oder Restore wartet oder läuft, oder ein Re-Embedding des ganzen Index, während eines aktiv ist. details.job_id nennt ihn |
JOB_NOT_CANCELLABLE | Abbruch eines fertigen Jobs oder eines Jobs in swapping |
SNAPSHOT_LIMIT_REACHED | Snapshot-Limit pro Index erreicht; mit ?prune=true erneut versuchen |
KEY_ALREADY_ROTATED / KEY_NOT_ACTIVE | Erneute Rotation / Rotation eines widerrufenen oder abgelaufenen Schlüssels |
413
| Code | Auslöser |
|---|---|
AUDIO_TOO_LARGE | Sprachaufnahme über der akzeptierten Größe |
422
| Code | Auslöser |
|---|---|
VALIDATION_ERROR | Zod-Schema lehnt ab — enthält details.issues |
IDEMPOTENCY_KEY_REUSED | Der Idempotency-Key wurde bereits für einen anderen Request verwendet (Methode, Pfad oder Body weichen ab) |
INVALID_DOCUMENT | Fehlender content, ungültiger Feldwert, mehr als 500 Dokumente pro Import oder 1000 pro Webhook |
INVALID_AUDIO | Audio-Payload lässt sich nicht dekodieren |
EMPTY_TRANSCRIPTION | Sprachaufnahme ohne erkannte Sprache |
CLIENT_ID_REQUIRED | POST /recent ohne x-aac-client-id |
QUERY_TOO_SHORT | POST /recent mit einer Anfrage unter 2 Zeichen |
INVALID_SNAPSHOT_NAME | DELETE /admin/snapshots mit unsicherem Dateinamen |
429
| Code | Auslöser |
|---|---|
RATE_LIMITED | Request-Rate pro Schlüssel überschritten |
QUOTA_EXCEEDED | Tarifkontingent der Metrik erschöpft |
5xx
| Code | Status | Auslöser |
|---|---|---|
INTERNAL_ERROR | 500 | Unbehandelte Ausnahme — request_id angeben |
FEATURE_UNSUPPORTED | 501 | Funktion braucht eine neuere Such-Engine als der verbundene Knoten (FEATURE_UNAVAILABLE für Funktionen, die in dieser Installation noch nicht aktiviert sind) |
SEARCH_UNAVAILABLE | 502 | Engine nicht erreichbar oder nicht zuordenbarer Fehler |
AI_UNAVAILABLE | 502 | Fehler des Modellanbieters |
AI_EMPTY_RESULT | 502 | Modell lieferte nichts Verwertbares |
SNAPSHOT_FAILED | 502 | Engine-Snapshot fehlgeschlagen |
SNAPSHOT_NOT_CONFIGURED | 503 | POST /admin/snapshot ohne SEARCH_SNAPSHOT_DIR |
SEARCH_UNAVAILABLE | 503 | Installation ohne Credentials für die Such-Engine |
AI_NOT_CONFIGURED | 503 | OPENAI_API_KEY fehlt für Antworten, Bilder oder Transkription |
NL_MODEL_NOT_CONFIGURED | 503 | Das für diese Funktion erforderliche Modell ist nicht konfiguriert |
VOICE_MODEL_NOT_CONFIGURED | 503 | Das für diese Funktion erforderliche Modell ist nicht konfiguriert |
CONVERSATION_MODEL_NOT_CONFIGURED | 503 | Das für diese Funktion erforderliche Modell ist nicht konfiguriert |
WEBHOOK_NOT_CONFIGURED | 503 | Kein Webhook-Geheimnis am Index oder Server |
SEARCH_TIMEOUT | 504 | Engine antwortet nicht rechtzeitig |
Engine- und Anbieterfehler werden auf diesen öffentlichen Vertrag abgebildet, bevor sie die API verlassen; interne Namen erscheinen nie in einer Antwort: 404 der Engine wird INDEX_NOT_FOUND, 409 wird INDEX_ALREADY_EXISTS, 400/422 werden INVALID_QUERY (ein unbekanntes Feld in filter_by/sort_by/facet_by/query_by/group_by wird FIELD_NOT_IN_SCHEMA), Timeouts werden SEARCH_TIMEOUT, alles andere (auch eine fehlende Engine-Konfiguration) wird SEARCH_UNAVAILABLE. Weitere Aliase: AI_PROVIDER_ERROR → AI_UNAVAILABLE, ENGINE_FEATURE_UNSUPPORTED → FEATURE_UNSUPPORTED, INDEX_HAS_NO_EMBEDDING_FIELD → INDEX_NOT_VECTOR_ENABLED. Die rohe Engine-Meldung wird serverseitig protokolliert und über request_id korreliert; nennen Sie diese ID dem Support.
Rate-Limits
Requests werden pro API-Schlüssel in einem gleitenden Fenster begrenzt:
| Einstellung | Standard | Env |
|---|---|---|
| Fenster | 60 Sekunden | SEARCH_RATE_WINDOW_SECONDS |
| Requests pro Fenster | 600 | SEARCH_RATE_MAX_REQUESTS |
Jede authentifizierte Antwort trägt x-ratelimit-limit, x-ratelimit-remaining und x-ratelimit-reset (Unix-Sekunden), auch erfolgreiche — ein Client kann sich also selbst takten. Überschreitung ergibt eine 429 mit Reset-Zeit:
curl -sS -i "$AAC_API/indexes/documentation/search?q=preise" \
-H "Authorization: Bearer $AAC_KEY"{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded",
"request_id": "req_9b1d3f5a7c9e1b3d5f7a9c1e",
"details": { "limit": 600, "reset_at": "2026-10-23T10:42:00.000Z" }
}
}Der Limiter läuft standardmäßig im Prozess und über Redis geteilt, wenn REDIS_URL gesetzt ist. Hinter mehreren Instanzen ohne Redis setzt jede Instanz ihre eigenen 600 durch — planen Sie das bei der Dimensionierung ein.
RATE_LIMITED und QUOTA_EXCEEDED sind beide 429, bedeuten aber Verschiedenes: das erste erledigt sich in Sekunden, das zweite erst mit der nächsten Abrechnungsperiode oder einem Tarifwechsel. Lesen Sie error.code, nicht nur den Status.
Tarifkontingente
Metriken sind searches, documents, ai_tokens und reindex. Inklusivmengen pro Abrechnungsperiode:
| Free | Pro (29 $/Monat) | Enterprise | |
|---|---|---|---|
| Suchen | 10.000 | 500.000 | 5.000.000 |
| Dokumente | 1.000 | 50.000 | 1.000.000 |
| KI-Token | 10.000 | 100.000 | 2.000.000 |
| Reindex-Läufe | 2 | 50 | 500 |
| Indizes | 5 | 10 | 100 |
| API-Schlüssel | 3 | 10 | 100 |
| Connectoren | 5 | 10 | 100 |
| Ergebnisse pro Seite | 50 | 250 | 500 |
| Aufbewahrung des Query-Logs | 7 Tage | 90 Tage | 365 Tage |
Überschreitungspreise pro Block:
| Metrik | Block | Free | Pro | Enterprise |
|---|---|---|---|---|
| Suchen | 1.000 | — | 50 ¢ | 25 ¢ |
| Dokumente | 1.000 | — | 20 ¢ | 10 ¢ |
| KI-Token | 1.000 | — | 2 ¢ | 1 ¢ |
| Reindex | 1 Lauf | — | 5 ¢ | 2 ¢ |
Free hat überhaupt keine Überschreitungspreise: Beim Erreichen eines Limits kommt 429 QUOTA_EXCEEDED, und es wird nichts belastet. Bei bezahlten Tarifen wird die Überschreitung mit dem Prepaid-Wallet verrechnet.
Tarifgebundene Funktionen: Free enthält Keyword-Suche, Synonyme, Stopwords, aiAnswer (RAG, im Rahmen von 10.000 KI-Token) und scheduledSync, aber kein vectorSearch, hybridSearch, curations, grouping, geoSearch, multiSearch, reindex, exportDocuments, customSort oder queryAnalytics. Die Nutzung einer gesperrten Funktion liefert 402 PAYMENT_REQUIRED aus der Metering-Schicht; der Request scheitert, statt still zu degradieren. Fehlender API-Schlüssel-Scope liefert weiterhin 403 FORBIDDEN.
Wallet und Überschreitung
- Die Nutzung wird pro Periode (
YYYY-MM) gezählt und am 1. um00:00 UTCzurückgesetzt. - Bis zur Inklusivmenge kostet ein Aufruf nichts.
- Darüber wird jeder Block aus dem Wallet belastet:
searchesin 1.000er-Blöcken,documentsundai_tokensebenso,reindexpro Lauf. - Bei Nullguthaben scheitert der Aufruf mit
402 PAYMENT_REQUIREDund Aufladehinweis; eine kostenpflichtige Tariffunktion scheitert ebenso. - Aufladungen sind Prepaid-Pakete zu 10, 50 und 200 USD, gekauft über den Zahlungsanbieter und im Ledger verbucht.
Lesen Sie den Zustand, bevor er weh tut:
curl -sS "$AAC_API/billing/wallet" -H "Authorization: Bearer $AAC_KEY"{
"wallet": { "balanceCents": 4200, "currency": "USD" },
"plan": "pro",
"period": "2026-10",
"resets_at": "2026-11-01T00:00:00.000Z",
"transactions": [
{
"id": "txn_01HZYA",
"type": "credit",
"amount_cents": 5000,
"balance_after_cents": 5000,
"description": "Top-up credits_50",
"metric": null,
"created_at": "2026-10-02T09:12:00.000Z"
},
{
"id": "txn_01HZYB",
"type": "debit",
"amount_cents": -800,
"balance_after_cents": 4200,
"description": "search overage: 16 blocks",
"metric": "searches",
"created_at": "2026-10-21T18:03:11.000Z"
}
],
"request_id": "req_4f6a8c0e2b4d6f8a0c2e4b6d"
}curl -sS "$AAC_API/billing/usage" -H "Authorization: Bearer $AAC_KEY"{
"usage": {
"plan": "pro",
"planName": "Pro",
"period": "2026-10",
"resetsAt": "2026-11-01T00:00:00.000Z",
"searches": { "used": 500000, "limit": 500000 },
"documents": { "used": 51204, "limit": 50000 },
"aiTokens": { "used": 24800, "limit": 100000 },
"indexes": { "used": 3, "limit": 10 },
"apiKeys": { "used": 2, "limit": 10 },
"connectors": { "used": 1, "limit": 10 },
"wallet": { "balanceCents": 4200, "currency": "USD" }
},
"request_id": "req_2b4d6f8a0c2e4b6d8f0a2c4e"
}documents wird aus dem Dokument-Spiegel gezählt, nicht aus einem Zähler, und spiegelt damit den tatsächlichen Indexinhalt. wallet ist null, wenn der Mandant nie ein Guthaben hatte.
Betriebsempfehlungen
- Nur bei 429 wiederholen.
Retry-Afterwird nicht gesendet; nutzen Siedetails.reset_atoderx-ratelimit-reset. - Keine 4xx außer 429 wiederholen. Eine 403 wird durch Wiederholung nicht zur 200, ein 422-Body muss korrigiert werden.
- 5xx mit Jitter wiederholen. Die SDKs versuchen es standardmäßig zweimal mit exponentiellem Backoff.
x-aac-quota-remainingbeobachten. Ein Scheitern an der Kontingentgrenze ist vermeidbar, wenn Sie bei 10 % Restwarnen.- Auf Null-Ergebnis-Rate alarmieren, nicht auf 200er. Eine Suche ohne Treffer ist per HTTP ein Erfolg und produktseitig ein Fehler.