Skip to content

Errors ​

Status: pilot. Draft — needs review before publishing.

Envelope ​

Every error uses one shape:

json
{
  "error": {
    "code": "insufficient_balance",
    "message": "Human-readable summary.",
    "details": {}
  }
}

code is the machine-readable value — branch on it, not on message, which is written for a person and may change. details carries field-level or domain-level specifics and may be empty.

Status codes ​

HTTPCodeWhen
400invalid_input, unsupported_imageValidation failed, unusable photo
401invalid_request, not_authenticatedMissing, malformed or expired token
402insufficient_balanceNot enough credit for the operation
404—Not found, or belongs to another account
409job_already_activeAn enrichment job is already running for the product
422ai_response_parsing_errorPermanent domain error, retrying will not help
429—Rate limit, on enrich and auth scopes
503ai_provider_error, research_failedTemporary provider failure, retry later

404 versus 403 ​

Another account's object returns 404, not 403. This is intentional: a 403 would confirm that the object exists.

Retrying ​

Only 503 and rate limits are worth an automatic retry. 422 is permanent — the job must be corrected, or retried through POST /jobs/{job_id}/retry/ after a fix, not by repeating the same request.

Known deviation ​

One path does not follow the envelope: PUT /products/{id}/content/ for a product that has no content yet returns 400 with a bare JSON list of strings, not the object above. A client that assumes the envelope everywhere will fail on that path. Treat PUT .../content/ as a special case until the backend is fixed.

Next ​