The error model
Every error response has the same envelope:Error envelope
A stable, machine-readable identifier. Branch on this — never on
message.A human-readable explanation. Safe to log; may change over time.
The HTTP status code, mirrored into the body for convenience.
Present on
VALIDATION_FAILED. Each entry names the offending path and what went wrong.X-AISearch-Version header so you can pin behavior to a known API version.
Error codes
No
Authorization header was sent. Add Authorization: Bearer <API_KEY>.The API key is malformed, revoked, or unknown.
The request body is invalid. Inspect
details[] for the specific fields.A value in
surfaces isn’t a supported surface. See the surfaces enum.The same
idempotencyKey was reused with a different body. Use a new key or resend the original body.You’ve exceeded your request rate. Honor
Retry-After and back off.Too many in-flight sync requests at once. Retry after a short delay, or use the async default.
The job queue is temporarily saturated. Back off and retry.
The job id doesn’t exist (or isn’t yours). Check the id and that it hasn’t expired.
A sync capture didn’t complete in time. Retry, or submit async and poll the job.
| Code | HTTP | When it happens |
|---|---|---|
AUTH_MISSING | 401 | No Authorization header. |
AUTH_INVALID | 401 | Key is malformed, revoked, or unknown. |
VALIDATION_FAILED | 400 | Body failed validation; see details[]. |
UNSUPPORTED_SURFACE | 400 | A surfaces value isn’t supported. |
IDEMPOTENCY_CONFLICT | 409 | Same idempotencyKey, different body. |
RATE_LIMIT_EXCEEDED | 429 | Request rate exceeded. |
CONCURRENCY_LIMIT_EXCEEDED | 429 | Too many concurrent sync requests. |
QUEUE_CAPACITY_EXCEEDED | 429 | Job queue temporarily saturated. |
JOB_NOT_FOUND | 404 | Unknown or expired job id. |
SURFACE_TIMEOUT | 504 | Sync capture didn’t finish in time. |
Rate limiting & concurrency
All three limit conditions return HTTP429. Every 429 carries the headers you need to back off precisely:
How long to wait before retrying. Always honor this value.
Your ceiling for the current window.
Requests left in the current window.
When the window resets and
Remaining refills.RATE_LIMIT_EXCEEDED— you’re sending too fast. WaitRetry-After, then resume. WatchX-RateLimit-Remainingto pace yourself before you hit the wall.CONCURRENCY_LIMIT_EXCEEDED— too many sync requests are open at once. Reduce parallelism, or switch to the async default and let jobs fan out server-side.QUEUE_CAPACITY_EXCEEDED— the queue is briefly full. Back off with jitter and retry; this clears on its own.
Backing off
Retry429 and 5xx responses with exponential backoff, capped, with jitter. When Retry-After is present, prefer it over your computed delay.
What isn’t an error
A surface returning nothing is not an error. The job still reachescompleted with provenance.surfacePresent: false, an empty answer, and a surface_absent warning — and an empty capture costs no credits. Handle it as data, not as a failure. See The Envelope for the full shape.
Job lifecycle
Terminal states, polling, and when to stop.
Webhooks
Skip polling entirely for fan-out jobs.