Errors
The Pixee PIM API uses standard HTTP status codes and returns structured JSON error responses when something goes wrong.
Error response format
Most error responses follow this unified envelope:
Error response
{
"error": {
"code": 404,
"error_code": "PRODUCT_NOT_FOUND",
"type": "not_found",
"message": "The EAN field is required.",
"details": null,
"path": "/api/v1/products/550e8400-e29b-41d4-a716-446655440000",
"request_id": "abc-123",
"timestamp": "2026-09-17T10:30:00Z"
}
}
| Field | Description |
|---|---|
error.code | HTTP status code, repeated in the body |
error.error_code | Standardized machine-readable code (e.g. PRODUCT_NOT_FOUND) |
error.type | Error category: validation_error, not_found, conflict, authentication_error, authorization_error, database_error, rate_limit_exceeded, internal_server_error, http_error |
error.message | Human-readable, localized description (see Localization below) |
error.details | Optional structured detail — e.g. a list of field errors on 422 |
error.path | The request path that produced the error |
error.request_id | Correlates with server-side logs — include it when reporting an issue |
error.timestamp | ISO 8601 timestamp of when the error occurred |
For 5xx errors, message is intentionally a generic, safe string (tied to request_id) — the original exception detail is never leaked to the client, only to server-side logs. For 4xx errors, message carries real, actionable information (e.g. "this SKU already exists").
Divergent shapes — endpoints that don't use the envelope above
A handful of error sources predate the unified envelope and were deliberately not rewritten to avoid silently changing an already-integrated contract. Match on the field names below, not on error.code:
| Situation | Status | Shape |
|---|---|---|
| Rate limit exceeded | 429 | { "error": "rate_limit_exceeded", "message", "detail", "retry_after" } — error is a string here. See Rate Limiting. |
| CSRF token missing/invalid | 403 | { "detail", "error_type": "csrf_validation_error", "debug": {...} } |
| Module disabled on this instance | 403 | { "detail", "code": "MODULE_DISABLED", "module_id": "..." } |
| Instance license suspended (read-only mode) | 403 | { "detail", "code": "LICENSE_SUSPENDED" } |
HTTP status codes
| Status | Meaning |
|---|---|
200 OK | Request succeeded |
201 Created | Resource created successfully |
202 Accepted | Request accepted — async processing started |
204 No Content | Request succeeded, no body returned |
400 Bad Request | Invalid request parameters |
401 Unauthorized | Missing, invalid, or expired credential |
403 Forbidden | Insufficient scope or permissions — or MODULE_DISABLED / LICENSE_SUSPENDED (see above) |
404 Not Found | Resource not found |
409 Conflict | Resource already exists (e.g. duplicate EAN) — or a dependency this operation needs is not configured on the instance (see Dependency not configured) |
422 Unprocessable Entity | Validation error on request body |
423 Locked | Account locked after repeated failed login attempts |
429 Too Many Requests | Rate limit exceeded — see Rate Limiting |
500 Internal Server Error | Unexpected server error |
503 Service Unavailable | A configured dependency is momentarily unreachable, or maintenance. Always carries a Retry-After header and error_code: SYSTEM_SERVICE_UNAVAILABLE — retry after the delay. Since September 18, 2026 a missing configuration is never a 503 |
Dependency not configured (409)
Some operations depend on something an administrator sets up per instance: an Odoo connection, a connector's webhook secret, an AI provider assigned to a usage, the shared key of the price monitoring service, the optional vector search engine. When that setup is missing, the API answers 409 Conflict with an error_code from the *_NOT_CONFIGURED family. This is a permanent state of the instance, not an outage: retrying will not help, fixing the configuration will. error.details names the dependency and the fix.
409 — dependency not configured
{
"error": {
"code": 409,
"error_code": "ODOO_NOT_CONFIGURED",
"type": "conflict",
"message": "No Odoo connection is configured on this instance. Enter the Odoo URL, database and credentials in the Odoo Sync screen before running this import.",
"details": { "dependency": "odoo", "how_to_fix": "Renseignez l'URL, la base et les identifiants Odoo dans l'écran Odoo Sync (/odoo-sync)" },
"path": "/api/v1/brands/import-from-odoo",
"request_id": "abc-123",
"timestamp": "2026-09-18T10:30:00Z"
}
}
| Error code | Raised by | What to do |
|---|---|---|
ODOO_NOT_CONFIGURED | POST /brands/import-from-odoo | An administrator enters the Odoo URL, database and credentials in the Odoo Sync screen |
CONNECTOR_WEBHOOK_NOT_CONFIGURED | POST /connectors/{shopify,woocommerce,bigcommerce}/webhook (409), POST /connectors/magento/push (401) | The connector or its webhook secret is missing on the PixeePIM side — the sender's credentials are not at fault |
AI_PROVIDER_NOT_CONFIGURED | POST /enrichment/products/{product_id} | Add a provider key in Settings → AI Services, or assign a provider to the “enrichment” usage |
SCRAPER_SYNC_KEY_NOT_CONFIGURED | GET /prices/export-watchlist, POST /prices/observations | The shared key with the price monitoring service is not set on this instance — ask an administrator |
SEARCH_VECTOR_NOT_CONFIGURED | GET /search/products/{product_id}/similar | Similarity search is an optional add-on; ask an administrator to enable it on the instance |
How to tell the two apart. 409 + *_NOT_CONFIGURED: the dependency was never set up — surface the message to whoever administers the instance and do not retry automatically. 503 + SYSTEM_SERVICE_UNAVAILABLE: the dependency is configured but unreachable right now — honour Retry-After and retry. Before September 18, 2026 both cases answered 503; integrations that treated those as transient outages should update their handling.
Validation errors (422)
Field-level validation failures populate error.details as an array:
422 Response
{
"error": {
"code": 422,
"error_code": "VALIDATION_FAILED",
"type": "validation_error",
"message": "Request validation failed",
"details": [
{ "field": "body.name", "message": "Field required", "type": "missing" }
],
"path": "/api/v1/products",
"request_id": "abc-123",
"timestamp": "2026-09-17T10:30:00Z"
}
}
Common error codes
| Error code | Description |
|---|---|
AUTHENTICATION_REQUIRED | No credential provided |
INVALID_API_KEY | API key is invalid or revoked |
PERMISSION_DENIED | Credential does not have access to this resource |
NOT_FOUND / PRODUCT_NOT_FOUND / IMPORT_JOB_NOT_FOUND | The requested resource does not exist |
VALIDATION_FAILED | One or more fields failed validation |
DUPLICATE_EAN | A product with this EAN already exists |
RATE_LIMIT_EXCEEDED | Too many requests — retry after the indicated delay |
IMPORT_PARSE_FAILED / IMPORT_PERSISTENCE_FAILED | Import job encountered errors — check the error report |
ACCOUNT_LOCKED | Too many failed login attempts — try again later |
MODULE_DISABLED | The module owning this route is switched off on this instance |
LICENSE_SUSPENDED | The instance's license is suspended — write operations are blocked (read-only mode) |
*_NOT_CONFIGURED | A per-instance dependency is missing — 409, see Dependency not configured |
SYSTEM_SERVICE_UNAVAILABLE | A configured dependency is momentarily unreachable — 503 with Retry-After |
SYSTEM_INTERNAL_ERROR | Unexpected server error (5xx) |
Localization
error.message is translated based on the Accept-Language header. Only French (fr) and English (en) have translated message catalogs today, even though the underlying locale middleware accepts a broader set of locales for other purposes.
The response's Content-Language header always announces the language that was actually served, not necessarily the one you requested. If you ask for a locale with no translated catalog, the API falls back to English (or French, depending on configuration) and says so honestly via Content-Language — it does not claim to speak a language it didn't use.
Request a localized error
curl https://api.pixeepim.com/api/v1/products/00000000-0000-0000-0000-000000000000 \
-H "Authorization: Bearer {api_key}" \
-H "Accept-Language: fr"
Async operations
Endpoints that start long-running jobs (imports, exports, enrichment) return 202 Accepted immediately. Poll the job's status endpoint until the status is completed or failed:
202 Accepted
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "processing"
}
For 429 errors, parse the detail field (e.g. "5 per 15 minute") — there is no numeric retry_after_seconds field; retry_after is a human-readable string. See Rate Limiting for the exact format and recommended backoff strategy.