Errors¶
Every refusal of the Ziex Try-On API has the same shape, whichever endpoint it comes from:
{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Please slow down.",
"details": []
}
}
codeis a stable, machine-readable string. The codes are listed below.messageis an English sentence for a log line. It can change at any time.detailsappears only on422 VALIDATION_ERRORfor a malformed request body. Each item is{"loc": [...], "msg": "...", "type": "..."};locnames the field.
The request id of an answer produced by the application is in the X-Request-ID response header. Quote it when you
ask for help (see Contact). Answers produced by the edge in front of the application (413, 429, 502,
503, 504 - see Edge errors) and the application's own 500 INTERNAL_ERROR have no request id.
Rules for handling errors¶
- Decide by
codeand the HTTP status, never by the text ofmessage. Texts are not part of the contract. - Never show the server's text to a shopper. Map the code to your own wording. The tables below give a short suggestion for every code. The widget and the plugins do exactly this.
- Read
Retry-After. Every429,409and503that the API produces carries it, in whole seconds. Wait at least that long before the next request. See Limits for what each value means. - Treat the HTTP status as the class (4xx: your request or your account; 5xx: ours) and the
codeas the condition.
Retries and uncertain answers¶
A try-on costs money, so the one thing to get right is what to do when you do not know whether a job was created. Send
an Idempotency-Key with every POST /api/v1/tryon (22 to 64 characters from A-Z a-z 0-9 _ -; a UUID without
dashes is a good key). The key is scoped to your API key and to its kind (secret or publishable). One key creates at
most one job.
| Answer to the POST | What it means | What to do |
|---|---|---|
201 |
The job exists and is pending. |
Follow it: GET /api/v1/tryon/status/{job_id}. |
200 with the header Idempotent-Replayed: true |
You repeated a key that already created a job. Nothing was charged a second time. | Use the job in the answer. Its status is the job's current status (it may already be completed or failed). A browser page cannot read this header (it is not in Access-Control-Expose-Headers): there, the 200 itself says it is a repeat. |
409 IDEMPOTENT_IN_PROGRESS |
The first request with this key is still being handled. | Repeat the same request with the same key after Retry-After (2 s). |
503 OVERLOADED + Retry-After: 5, no X-Job-Id |
Every image-processing slot stayed busy. No job exists and nothing was charged. | Repeat with the same key after Retry-After. It is a new attempt, so one job and one charge. A few repeats are enough; the widget makes at most 3 repeats within 60 seconds. |
503 SUBMIT_UNCONFIRMED + X-Job-Id + Retry-After: 2 |
The job was recorded but we could not confirm that it was queued. It may be queued. | Poll GET /api/v1/tryon/status/{job_id} with the id from X-Job-Id, or GET /api/v1/tryon/attempts/{idempotency_key}. Never send it again under a new key - that could create and charge a second job. |
500 INTERNAL_ERROR |
The final answer of this attempt. If the job had already been recorded, it exists and has been marked failed; its plan unit is given back automatically. |
Do not loop on it. A repeat with the same key answers 200 with that failed job (or, when no job was recorded, starts a new attempt). To try again, use a new Idempotency-Key. |
502, 504, 503 SERVICE_UNAVAILABLE, a dropped connection, a timeout |
You do not know whether the request reached the handler. | Look the attempt up: GET /api/v1/tryon/attempts/{idempotency_key}. 200: the job exists, follow it. 409 IDEMPOTENT_IN_PROGRESS: ask again after Retry-After. 404 NOT_FOUND: ask again for about two minutes (the request may still be on its way); if it is still 404 after that, no job was created and you can submit again with a new key. 503 ATTEMPT_LOOKUP_UNAVAILABLE: ask again after Retry-After. |
Any other 4xx |
Your request was refused and no job exists. | Fix the cause (see the tables). Repeating the same request will be refused the same way. |
A request that is refused before a job exists takes nothing from your plan: no generation, no counter, no marker. A repeat of the same key after such a refusal is simply a new attempt.
The key is remembered for as long as the job exists (at least 24 hours): a repeat with the same key always answers with the job that key created, so never reuse a key for a new try-on. GET /api/v1/tryon/attempts/{idempotency_key} answers like the status endpoint
(plus X-Job-Id and Location headers) and is scoped to your API key and its kind.
Webhooks are best effort¶
A webhook can be late, repeated or lost (your endpoint was down, a network failure, or the job was ended by the platform's own recovery). Never rely on a webhook alone. Reconcile by polling:
GET /api/v1/tryon/status/{job_id}for a job you know;GET /api/v1/tryon/jobs?since=...for the jobs of a secret key (the list of your jobs;sinceis an ISO 8601 time), for example once a minute for the jobs that are stillpendingorprocessing.
Every job reaches a final state (completed, failed or interrupted) within 45 minutes. A job that waits in
pending for more than 30 minutes, or runs in processing for more than 15 minutes, is ended by the platform as
failed. Such a job may send no webhook, so stop waiting at 45 minutes and read the status.
Authentication and access¶
These come from the key and the page it is used from. They are configuration problems: do not retry them in a loop. Show the shopper a neutral message and tell the shop owner.
| Code | HTTP | When | What to do | Show the shopper |
|---|---|---|---|---|
INVALID_API_KEY |
401 | The X-API-Key header is missing or empty, the key is unknown, the secret is wrong, a key longer than 72 bytes, or a test key while test keys are switched off. |
Do not retry. Check the key you send. A missing header is this code, not a validation error. | "Try-on is not available right now." |
KEY_EXPIRED |
401 | The key has passed its expiry date. | Do not retry. Issue a new key in the client portal and replace it. | "Try-on is not available right now." |
KEY_SUSPENDED |
403 | The key was switched off. | Do not retry. Switch the key on in the client portal, or issue a new one. | "Try-on is not available right now." |
CLIENT_SUSPENDED |
403 | The whole account is not active. | Do not retry. Contact us (see Contact). | "Try-on is not available right now." |
ORIGIN_REQUIRED |
403 | A publishable key was used with neither Origin nor Referer, that is, not from a browser page. |
Do not retry. A publishable key works only from a page; call from your server with the secret key instead. | "Try-on is not available right now." |
KEY_NOT_BOUND |
403 | A live publishable key has no domain bound to it. | Do not retry. Bind your shop's domain to the key in the client portal. | "Try-on is not available right now." |
DOMAIN_NOT_ALLOWED |
403 | The host of Origin is not the key's domain (nor www. or a subdomain of it). A live key used on a development host such as localhost gets the same code. |
Do not retry. Use the key on the bound domain, or use a test key on a development host (see Local development). | "Try-on is not available on this site." |
SECRET_KEY_REQUIRED |
403 | A publishable key called an endpoint that needs the secret key (GET /api/v1/tryon/integration, GET /api/v1/tryon/jobs). |
Do not retry. Call it from your server with the secret key. | Nothing: this is not a shopper-facing call. |
WEBHOOK_NOT_ALLOWED |
403 | A publishable key sent a webhook_url. |
Do not retry. Remove webhook_url, or submit from your server with the secret key. |
Nothing: your page should not send it. |
Request errors¶
| Code | HTTP | When | What to do | Show the shopper |
|---|---|---|---|---|
VALIDATION_ERROR |
422 | The body or a parameter is malformed (the details list names the field); the Idempotency-Key is malformed; a photo is rejected: unsupported format (allowed: JPEG, PNG, WebP, HEIC, AVIF), unreadable file, over 10 MB, over 16 megapixels (4096 x 4096) or under 256 x 256 pixels; a webhook_url that is not a public address. |
Do not retry the same request. Fix the field named in details, or ask the shopper for another photo. |
For a photo: "We cannot use this photo. Please choose another one." Otherwise: "Something went wrong. Please try again." |
INVALID_REQUEST |
400 | The request is malformed in a way the schema cannot name: a broken data URI or base64 text, or a job_id that is not a valid id. |
Do not retry the same request. Fix it. | "We cannot use this photo. Please choose another one." |
PHOTO_UPLOAD_REQUIRED |
422 | A publishable key or a test key sent the shopper's photo (model_image) as a URL. It must be an upload (a data: URI). The garment may stay a URL. |
Do not retry. Send the photo as an upload. | Nothing: the page should send an upload. |
CATEGORY_UNSUPPORTED |
422 | The category is unknown, or no model currently covers it. |
Do not retry. Read GET /api/v1/tryon/capabilities for the current categories. |
"This item cannot be tried on yet." |
NOT_FOUND |
404 | No such job for this key (a job of another key looks the same), no result, or no attempt for this Idempotency-Key. |
For an attempt lookup see the retry table above. Otherwise do not retry. | "We could not find this try-on." |
Limits and quota¶
All of them carry Retry-After. The numbers are on Limits. Refusals of this group happen before a job
exists, so they take nothing, and a repeat with the same Idempotency-Key after the wait is safe.
| Code | HTTP | When | What to do | Show the shopper |
|---|---|---|---|---|
RATE_LIMITED |
429 | A per-IP, per-key-and-IP or hourly per-key rate limit was hit, or the per-key limit of GET /api/v1/tryon/jobs (60 a minute). |
Wait Retry-After seconds and repeat. Slow down; do not retry at once. |
"Too many try-ons. Please wait a moment and try again." |
LIMIT_EXCEEDED |
429 | The plan's generations for the billing period are used up (with a 10 % tolerance). | Retrying does not help before the reset: Retry-After is the time to the reset (at least 60 s; one day when no reset date is set). Raise the plan or wait. Tell the shop owner. |
"Try-on is not available right now." |
DAILY_CAP_REACHED |
429 | The platform-wide daily cap or this key's share of it was reached. | Retry-After is the time to the next UTC midnight. Do not loop. Tell the shop owner. |
"Try-on is not available right now. Please try again tomorrow." |
OVERLOADED |
503 | Every image-processing slot stayed busy for 10 seconds. No job exists. | See the retry table: repeat with the same key after Retry-After (5 s). |
"Busy right now. Please wait a few seconds." |
Retries and server-side conditions¶
| Code | HTTP | When | What to do | Show the shopper |
|---|---|---|---|---|
IDEMPOTENT_IN_PROGRESS |
409 | A request with this key is still being handled (on POST /api/v1/tryon, or when looking an attempt up). |
Repeat with the same key after Retry-After (2 s). |
Keep showing "Working on it". |
SUBMIT_UNCONFIRMED |
503 | The job was recorded; whether it was queued is not confirmed. The answer carries X-Job-Id and Retry-After: 2. |
Poll the job or look the attempt up. Never resubmit under a new key. | Keep showing "Working on it". |
ATTEMPT_LOOKUP_UNAVAILABLE |
503 | GET /api/v1/tryon/attempts/{idempotency_key} cannot tell whether the attempt is running or does not exist. |
Ask again after Retry-After (2 s). Do not conclude that no job exists. |
Keep showing "Working on it". |
INTERNAL_ERROR |
500 | An unexpected error. See the retry table: it is final for this attempt. | New attempt only with a new Idempotency-Key. |
"Something went wrong. Please try again." |
SERVICE_UNAVAILABLE |
502, 503, 504 | The service is restarting or did not answer in time. This code is produced by the edge (see below). | Look the attempt up before you submit again. Back off: wait a few seconds, then 2x, 4x. | "The service is busy. Please try again in a moment." |
Codes that follow the status¶
A few codes appear only when the API answers with a plain status and no specific condition. The public try-on endpoints use a specific code above wherever there is one, so you will rarely see these there; they are listed so that a client can handle them.
| Code | HTTP | Meaning | What to do | Show the shopper |
|---|---|---|---|---|
AUTHENTICATION_ERROR |
401 | The caller is not authenticated. | Do not retry. Fix the credentials. | "Try-on is not available right now." |
AUTHORIZATION_ERROR |
403 | The caller is not allowed. | Do not retry. | "Try-on is not available right now." |
CONFLICT |
409 | The request conflicts with the current state. | Do not retry the same request. | "Something went wrong. Please try again." |
Test keys and the client portal¶
These codes belong to the client portal and the test-key and webhook tools, not to the try-on endpoints.
| Code | HTTP | When | What to do |
|---|---|---|---|
TEST_KEY_NOT_ALLOWED |
401 | A test key tried to sign in to the client portal (or a portal session of a test key was used). A test key opens no portal. | Sign in with the live secret key. |
TEST_KEY_LIMIT |
409 | The account already has the maximum number of test keys (3). No Retry-After: waiting does not help. |
Delete a test key, then create the new one. |
WEBHOOK_LIMIT |
409 | The account already has the maximum number of webhook endpoints (10, live and test together); this is the 11th. No Retry-After: waiting does not help. |
Delete an unused endpoint, then create the new one. This is not for the shopper: show the shop owner "Delete an unused webhook endpoint". |
Edge errors¶
The edge in front of the API answers these before the application sees the request. They use the same envelope, with
Access-Control-Allow-Origin: *, and have no X-Request-ID.
| Code | HTTP | When | What to do | Show the shopper |
|---|---|---|---|---|
PAYLOAD_TOO_LARGE |
413 | The request body is over 12 MB. | Do not retry. Send smaller photos (they are shrunk to about 1600 px on the long side in the widget; do the same). | "This photo is too large. Please choose a smaller one." |
RATE_LIMITED |
429 | The request rate from your address is over the per-address limit. Retry-After: 1. |
Wait the seconds in Retry-After and repeat. If you proxy all shoppers from one server, slow down: see Limits. |
"Too many try-ons. Please wait a moment and try again." |
SERVICE_UNAVAILABLE |
502, 503, 504 | The service is restarting, or did not answer in time. | See the retry table: look the attempt up before you submit again. | "The service is busy. Please try again in a moment." |
Errors of a job¶
A job that ends failed or interrupted has an error_code and a safe error_message in the status answer
(GET /api/v1/tryon/status/{job_id}). They carry no provider detail. These are not HTTP error codes: the status
request itself is 200.
error_code |
Meaning | What to do | Show the shopper |
|---|---|---|---|
GENERATION_FAILED |
The result could not be generated. The default for anything not listed below. | Offer a retry. It is a new job, so send a new Idempotency-Key. The failed job's plan unit comes back automatically. |
"We could not generate this try-on. Please try again." |
INVALID_IMAGE |
One of the photos could not be processed. | A retry with the same photo will fail again. Ask for another photo. | "One of the photos could not be processed. Please try a different photo." |
CONTENT_BLOCKED |
The image cannot be used for a try-on. | Do not retry with the same photo. | "This image cannot be used for a try-on. Please choose a different photo." |
TIMEOUT |
It took longer than expected, or a worker was lost. | Offer a retry with a new Idempotency-Key. |
"This took longer than expected. Please try again." |
INTERRUPTED |
The try-on was interrupted (for example by a restart). | Offer a retry with a new Idempotency-Key. |
"The try-on was interrupted. Please try again." |
A failed or interrupted job gives its generation back to the plan. A completed job is never refunded.
site.reason and the codes¶
GET /api/v1/tryon/integration?site_url=... (secret key) answers whether a key may be used on a site, as site.allowed
and, when it may not, site.reason. The reasons are lowercase words and map onto the codes that the same site would
get from a real request:
site.reason |
Code on a real request | Meaning |
|---|---|---|
key_not_bound |
KEY_NOT_BOUND |
The live key has no domain bound to it. |
domain_mismatch |
DOMAIN_NOT_ALLOWED |
The site is not the key's domain. |
dev_host |
DOMAIN_NOT_ALLOWED |
A live key on a development host (localhost, *.test, a private IP). Use a test key there. |
Where to go next¶
- The numbers behind every
429and503: Limits. - Every endpoint with its documented answers: API reference.
- A first call from a server: Quickstart B.