AACSearch
API-Referenz

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

CodeAuslöser
INVALID_QUERYFehlerhafte 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_SCHEMAfilter_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_JSONWebhook-Body, der kein JSON ist
PRIVATE_FIELDquery_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_PRESENTPOST /indexes/{slug}/scoped-key für einen Index mit privaten Feldern durch einen Schlüssel ohne documents:read_private. details.fields

401

CodeAuslöser
UNAUTHORIZEDFehlender oder fehlerhafter Authorization-Header, unbekannter oder widerrufener Schlüssel, fehlende Signatur-Header beim Webhook
KEY_ROTATEDDer 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_REVOKEDDer 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_SIGNATUREWebhook-Zeitstempel außerhalb des 300-Sekunden-Fensters
INVALID_SIGNATUREHMAC des Webhooks passt nicht

402

CodeAuslöser
PAYMENT_REQUIREDDie 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_REACHEDEin weiterer Index oder API-Schlüssel bzw. ein Dokumentimport würde ein Tariflimit (Indizes, Schlüssel oder Dokumente) überschreiten
FEATURE_NOT_AVAILABLEOrganisation suspended nach Zahlungsausfall: läuft als Free, bezahlte Funktionen werden mit details.reason = "past_due" abgelehnt — offene Rechnung bezahlen, nicht upgraden

403

CodeAuslöser
FORBIDDENDem 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_ALLOWEDEin 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_MISMATCHEmbed-Session ist auf einen anderen Index beschränkt als den angefragten
KEY_IN_GRACEDer 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_CALLERPOST /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

CodeAuslöser
INDEX_NOT_FOUNDUnbekannter 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_FOUNDGET/DELETE auf eine unbekannte external_id
PROFILE_NOT_FOUND?profile= nennt ein Profil, das der Index nicht hat
CONVERSATION_NOT_FOUNDDialog-ID, die nicht zum Index gehört
NOT_FOUND/metrics ohne gültiges x-metrics-token
JOB_NOT_FOUNDJob eines anderen Mandanten oder (indexgebundener Schlüssel) eines anderen Index
SNAPSHOT_NOT_FOUNDUnbekannter Snapshot oder einer eines anderen Index/Mandanten

409

CodeAuslöser
INDEX_ALREADY_EXISTSDuplikat auf Engine-Ebene beim Anlegen einer Collection (Reindex, Konsole); der physische Collection-Name wird nie offengelegt
CONFLICTKonfliktierender Schreibvorgang, erkannt in der Service-Schicht
IDEMPOTENCY_IN_FLIGHTEin Dokumentimport mit demselben Idempotency-Key läuft noch — nach dessen Ende erneut versuchen
JOB_CONFLICTReindex 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_CANCELLABLEAbbruch eines fertigen Jobs oder eines Jobs in swapping
SNAPSHOT_LIMIT_REACHEDSnapshot-Limit pro Index erreicht; mit ?prune=true erneut versuchen
KEY_ALREADY_ROTATED / KEY_NOT_ACTIVEErneute Rotation / Rotation eines widerrufenen oder abgelaufenen Schlüssels

413

CodeAuslöser
AUDIO_TOO_LARGESprachaufnahme über der akzeptierten Größe

422

CodeAuslöser
VALIDATION_ERRORZod-Schema lehnt ab — enthält details.issues
IDEMPOTENCY_KEY_REUSEDDer Idempotency-Key wurde bereits für einen anderen Request verwendet (Methode, Pfad oder Body weichen ab)
INVALID_DOCUMENTFehlender content, ungültiger Feldwert, mehr als 500 Dokumente pro Import oder 1000 pro Webhook
INVALID_AUDIOAudio-Payload lässt sich nicht dekodieren
EMPTY_TRANSCRIPTIONSprachaufnahme ohne erkannte Sprache
CLIENT_ID_REQUIREDPOST /recent ohne x-aac-client-id
QUERY_TOO_SHORTPOST /recent mit einer Anfrage unter 2 Zeichen
INVALID_SNAPSHOT_NAMEDELETE /admin/snapshots mit unsicherem Dateinamen

429

CodeAuslöser
RATE_LIMITEDRequest-Rate pro Schlüssel überschritten
QUOTA_EXCEEDEDTarifkontingent der Metrik erschöpft

5xx

CodeStatusAuslöser
INTERNAL_ERROR500Unbehandelte Ausnahme — request_id angeben
FEATURE_UNSUPPORTED501Funktion braucht eine neuere Such-Engine als der verbundene Knoten (FEATURE_UNAVAILABLE für Funktionen, die in dieser Installation noch nicht aktiviert sind)
SEARCH_UNAVAILABLE502Engine nicht erreichbar oder nicht zuordenbarer Fehler
AI_UNAVAILABLE502Fehler des Modellanbieters
AI_EMPTY_RESULT502Modell lieferte nichts Verwertbares
SNAPSHOT_FAILED502Engine-Snapshot fehlgeschlagen
SNAPSHOT_NOT_CONFIGURED503POST /admin/snapshot ohne SEARCH_SNAPSHOT_DIR
SEARCH_UNAVAILABLE503Installation ohne Credentials für die Such-Engine
AI_NOT_CONFIGURED503OPENAI_API_KEY fehlt für Antworten, Bilder oder Transkription
NL_MODEL_NOT_CONFIGURED503Das für diese Funktion erforderliche Modell ist nicht konfiguriert
VOICE_MODEL_NOT_CONFIGURED503Das für diese Funktion erforderliche Modell ist nicht konfiguriert
CONVERSATION_MODEL_NOT_CONFIGURED503Das für diese Funktion erforderliche Modell ist nicht konfiguriert
WEBHOOK_NOT_CONFIGURED503Kein Webhook-Geheimnis am Index oder Server
SEARCH_TIMEOUT504Engine 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:

EinstellungStandardEnv
Fenster60 SekundenSEARCH_RATE_WINDOW_SECONDS
Requests pro Fenster600SEARCH_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:

FreePro (29 $/Monat)Enterprise
Suchen10.000500.0005.000.000
Dokumente1.00050.0001.000.000
KI-Token10.000100.0002.000.000
Reindex-Läufe250500
Indizes510100
API-Schlüssel310100
Connectoren510100
Ergebnisse pro Seite50250500
Aufbewahrung des Query-Logs7 Tage90 Tage365 Tage

Überschreitungspreise pro Block:

MetrikBlockFreeProEnterprise
Suchen1.000—50 ¢25 ¢
Dokumente1.000—20 ¢10 ¢
KI-Token1.000—2 ¢1 ¢
Reindex1 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

  1. Die Nutzung wird pro Periode (YYYY-MM) gezählt und am 1. um 00:00 UTC zurückgesetzt.
  2. Bis zur Inklusivmenge kostet ein Aufruf nichts.
  3. Darüber wird jeder Block aus dem Wallet belastet: searches in 1.000er-Blöcken, documents und ai_tokens ebenso, reindex pro Lauf.
  4. Bei Nullguthaben scheitert der Aufruf mit 402 PAYMENT_REQUIRED und Aufladehinweis; eine kostenpflichtige Tariffunktion scheitert ebenso.
  5. 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-After wird nicht gesendet; nutzen Sie details.reset_at oder x-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-remaining beobachten. 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.

On this page