AACSearch / guide
Errors and retries
The JSON error body, every status code the tenant API answers with, which failures are worth retrying, and how Idempotency-Key protects a retried write.
Every non-2xx answer from the tenant API (/v1) has a JSON body with a
human-readable message. Branch on the HTTP status, not on the text of the
message. The message is meant for logs and people and can change.
The error body
There are two shapes, depending on which layer refused the request.
Gateway and search-engine operations. Authentication, key scope, plan, quota
and rate-limit checks run in front of every operation. Operations forwarded to
the search engine (collections, documents, searchCollection, multiSearch, …)
answer in the same plain shape:
{ "message": "Forbidden - this API key may not access the collection \"orders\"." }Some of these carry extra fields next to message:
| Status | Extra fields |
|---|---|
402 organization paused | reason: "payment" (a subscription payment failed) or "manual" (an owner paused the API) |
403 feature not in plan | feature (e.g. "experiments"), plan (the organization's current plan) |
429 monthly quota used up | resets_at (Unix milliseconds) |
Operations the control plane implements itself. These include events, recommendations, suggestions, experiments, connectors, assistants, voice, media and semantic or image search. They answer with a few more fields:
{
"defined": false,
"code": "CONFLICT",
"status": 409,
"message": "The experiment is already running.",
"data": {}
}code is a stable name for the status (BAD_REQUEST, UNAUTHORIZED,
PAYMENT_REQUIRED, FORBIDDEN, NOT_FOUND, CONFLICT, TOO_MANY_REQUESTS,
INTERNAL_SERVER_ERROR). data holds details when there are any:
| Case | data |
|---|---|
Request failed schema validation (400, message Input validation failed) | issues: one entry per invalid field |
Wallet cannot cover an AI operation (402) | available, requested: credits |
A search key's per-session AI allowance is used up (429) | retry_after: seconds |
The contract documents the common part: message (schema ApiResponse), plus
available / requested on 402 (schema InsufficientCredits). A client that
reads message works with both shapes. For the numbers, read data.available
first and fall back to a top-level available.
Status codes
| Status | When | Retry? |
|---|---|---|
400 | Invalid body or query (Input validation failed), a request body that is not valid JSON, a field the key's field allowlist does not include, a missing X-NOVA-SESSION-ID on an AI operation made with a search key, an image over 8 MB or audio over 25 MB | No. Fix the request |
401 | No X-NOVA-API-KEY header, or a key that is unknown, expired or revoked. Also a secured key whose signature is invalid or has expired | No. Fix the key |
402 | The organization is paused (every operation answers 402 until it is settled or resumed), or the wallet cannot cover an AI operation | Only after a top-up or resume |
403 | The key's scope does not reach the operation, the operation's feature is not in the plan, the collection is not on the key's allowlist, the request's IP or Origin is not allowed, a key's row- or field-level restrictions cannot be applied to the operation, or a search key asked for another session's conversation | No |
404 | A path that is not in the contract (Not Found), or a resource that does not exist (Experiment … not found.) | No |
409 | The resource's current state rules the request out. See Conflicts | After the state changes |
411 | A raw upload without Content-Length (putMediaContent, uploadIngestFeed) | No |
413 | putMediaContent over 100 MB, uploadIngestFeed over 500 MB | No. See limits |
429 | Monthly quota, a rate limit or the per-session AI allowance. See limits | Yes, after the wait |
500 | An unexpected error in a control-plane operation (Internal server error), or the credit wallet was unreachable | Yes, with backoff |
502 | The search engine could not be reached (Search engine unavailable, retry later.) | Yes, with backoff |
503 | The engine's circuit breaker is open. Sent with Retry-After: 10 | Yes, after the wait |
The API does not send 422. Validation failures are 400.
Conflicts
409 always means "not in this state". The message says which state:
| Operation | Conflict |
|---|---|
startExperiment | The experiment is already running |
updateExperiment | The experiment is running: stop it before changing it |
rollbackExperimentPromotion | No reversible promotion, or the configuration changed since |
reindexCollection | A reindex of the collection is already queued or running (the message names the job) |
putMediaContent | The asset already has content |
createConnector | The Idempotency-Key was already used with a different body |
Retrying
- Honour
Retry-After. Every429from a quota or rate limit carries it, in seconds. For the monthly quota it counts down to the reset, so it can be days. Show "quota exceeded" rather than sleeping. The per-session AI allowance sends the wait indata.retry_afterinstead of the header. - Back off on
500,502and503. Use exponential backoff with jitter, and stop after a few attempts. - Do not retry
400,401,403,404,411or413. The same request fails the same way. - Failed requests do not use quota. Only a successful answer from the search engine counts against the monthly quota. A failed AI call costs no credits, and its reservation is released.
The generated SDKs raise a typed error for
each status. By default they retry 408, 429 and 5xx with backoff and
honour Retry-After.
Idempotency
A retried write can land twice if the first attempt succeeded but the response was lost. There are two protections:
Idempotency-KeyoncreateConnector(POST /connectors). Send a unique value, up to 255 characters. A retry with the same key and the same body returns the original response, including the connector secret that is shown only once. The same key with a different body answers409. Other operations ignore the header.event_idon events.ingestEventsdrops an event whoseevent_idit has already stored and reports it indeduplicated. A batch is safe to resend when every event has one (see events).
Other writes have no idempotency key. Before retrying a create, check whether
the first attempt landed, for example with listExperiments.
Outbound event delivery has its own retry policy — six attempts with jittered
backoff, and only 408, 429 and 5xx are retried. See
Webhooks.
Limits and quotas
The monthly request quota per plan and what counts against it, rate limits per key scope, the per-session AI allowance, and request size limits.
Webhooks
Receive signed AACSearch events on your own endpoint — subscribe, verify the HMAC signature, handle retries, and diagnose failed deliveries.