API Error Reference

Every error code the Visual Search API can return, with cause and recommended fix.

← Back to Developer portal

Error envelope

All API errors return JSON with the same shape:

{
  "error": "image_download_failed",           // machine-readable code
  "message": "Failed to download image: HTTP 404: Not Found",  // human-readable
  "details": { "url": "https://example.com/photo.jpg" },       // optional context
  "request_id": "a1b2c3d4e5f6..."             // quote this in support tickets
}

The request_id is also echoed in the X-Request-ID response header. Rate-limit 429s additionally carry X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and Retry-After.

Retries should be safe. Attach an Idempotency-Key header (any unique string up to 255 chars) on POST / PUT / PATCH / DELETE requests. If the same key is replayed within 24 h the server returns the original response instead of executing the operation again. Duplicate keys with a different body return 409 idempotency_conflict.

Authentication & authorization

CodeHTTPCauseHow to fix
unauthorized 401 Missing or invalid Authorization: Bearer … header. Obtain a token via POST /oauth/token (client-credentials) or POST /oauth/login. Attach as Authorization: Bearer <token>.
token_expired 401 Access token's exp claim has passed. Exchange the old token for a fresh one via POST /oauth/refresh (5-minute grace window). The old token is revoked atomically.
token_revoked 401 Token was explicitly revoked via POST /oauth/revoke or rotated. Re-authenticate. The old token cannot be reused.
forbidden 403 Token is valid but the caller lacks the required scope / permission. Check the user's group permissions (GET /v1/security/permissions/me), or escalate to a client-owner token.

Validation & input

CodeHTTPCauseHow to fix
validation_error 422 Request body failed Pydantic validation (wrong types, missing required field, out-of-range values). Read details[] — each entry has loc (field path), type (error kind), and msg (human explanation). Fix the offending field and retry.
not_found 404 The product / client / webhook / job referenced by path or body doesn't exist (or doesn't belong to the caller's tenant). Verify the ID exists via the corresponding GET endpoint, or re-index.
already_exists 409 A resource with the same primary key already exists (duplicate client email, duplicate product ID, etc.). Use a different identifier, or use PUT to update the existing resource.
invalid_url 400 The supplied URL resolves to a private / restricted network (SSRF protection). Use a publicly routable URL. The server refuses RFC 1918, loopback, link-local, and metadata IP addresses.

Image handling

CodeHTTPCauseHow to fix
image_download_failed 400 Couldn't download the URL you provided (404, DNS failure, timeout, TLS error, etc.). Verify the URL responds with 200 and returns an image body. details.url echoes the URL we tried.
unsupported_format 415 The downloaded bytes aren't a supported image format. Use JPEG, PNG, or WebP. HTML, PDFs, SVGs, and HEIC are rejected.
image_too_large 413 Image exceeds the maximum size limit. Compress or resize the image below 10 MB before indexing.
image_blocked_by_moderation 403 Image rejected by the tenant's content-moderation policy. Returned only when the moderation policy is in an enforce mode (moderate / strict) and the image scored above the category threshold. Inspect details.flagged_categories to see which categories tripped (e.g. nsfw_explicit, violence_gore). Fix the source asset, or relax the policy under /portal/moderation if it's a false positive.

Rate limits & plan

CodeHTTPCauseHow to fix
rate_limit_exceeded 429 Too many requests for the caller's plan rate-limit window. Honour the Retry-After header. Implement exponential back-off. Upgrade your plan for a higher ceiling.
too_many_requests 429 Per-identity throttle on /oauth/login and /oauth/token tripped — too many failed attempts for this email or client_id (regardless of source IP). Resets after the lockout window or on a successful auth. Wait for details.retry_after seconds, then try again. If you've forgotten your password, use the password-reset flow instead of guessing.
requires_2fa 401 Email + password were correct but the user has 2FA enabled and the request didn't include a totp_code. Returned only by /oauth/login. Prompt the user for their 6-digit authenticator code, then resend the same login request with totp_code populated. Don't retry without the code — it'll get the same response.
invalid_2fa 401 The totp_code in the login body didn't verify against the user's TOTP secret. Returned only by /oauth/login. Authenticator codes refresh every 30 seconds — re-prompt and try again with a fresh code. Counts against the per-identity login throttle, so don't retry indefinitely.
plan_limit_exceeded 403 Hit a hard quota (e.g. max products indexed for your plan). Delete unused products or upgrade. details includes current and max_allowed.
bulk_not_allowed 403 The current plan doesn't allow bulk indexing jobs. Upgrade to a plan with bulk_allowed: true, or index products one at a time.

Idempotency

CodeHTTPCauseHow to fix
idempotency_conflict 409 You reused an Idempotency-Key with a different request body / method / path. Use a new key for a new logical operation. Idempotency keys are scoped to the tenant and TTL'd for 24 h.
idempotency_in_progress 409 A concurrent request with the same key is still executing. Retry after a short delay (the middleware caches for 24 h once the first call finishes).
idempotency_invalid_key 400 Key contains non-printable characters or exceeds 255 bytes. Use a UUIDv4 or any opaque printable-ASCII string ≤ 255 chars.

Server-side

CodeHTTPCauseHow to fix
internal_error 500 Unexpected server error. Fully traced server-side. Retry (it's an idempotent-safe state if you used Idempotency-Key). If it persists, email [email protected] with the request_id.
model_loading 503 The CLIP model is still warming up after a cold start (rare). Retry after a few seconds. Typically resolves within 30 s of deploy.
service_unavailable 503 An upstream dependency (Qdrant, Stripe, AI proxy) is unreachable. Retry with back-off. Monitor /v1/ai/health and the system status page for current state.

Recommended request pattern

POST /v1/products/bulk
Authorization: Bearer 
Idempotency-Key: 7f0c4b2a-4e61-4d62-8a8f-9d30b1ff84aa
Content-Type: application/json

{ "products": [ ... ] }

On network failure, retry with the same Idempotency-Key. The server will return the original response instead of indexing twice. On 5xx keep retrying (those aren't cached). On 4xx fix the request and use a new key.