{"openapi":"3.1.0","info":{"title":"Ziex Try-On Public API","description":"The try-on API of Ziex Try-On: submit a virtual try-on, follow it to its result, read what the service can try on. Every error has the same shape, `{\"error\": {\"code\", \"message\", \"details\"?}}`; decide by `code`, never by `message`.","version":"1.1.0","contact":{"email":"dimanhawaikj2@gmail.com"},"x-changelog":[{"version":"1.1.0","date":"2026-10-26","changes":["First published specification: every error status with its codes, the request and response headers, the capabilities document, the webhooks."]}]},"servers":[{"url":"https://api.ziex-tryon.com"}],"paths":{"/api/v1/tryon":{"post":{"tags":["tryon"],"summary":"Submit a virtual try-on job","description":"Submit a try-on: the shopper's photo (`model_image`) and the garment (`garment_image`), each a data URI or an\nhttp(s) URL — with a publishable key the photo must be an upload. Send an `Idempotency-Key`: a repeat with it never\ncreates a second job, and a lost answer is found again with `GET /api/v1/tryon/attempts/{key}`. Then poll\n`GET /api/v1/tryon/status/{job_id}` or receive a webhook (`webhook_url`, secret key only).","operationId":"create_tryon_api_v1_tryon_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TryOnRequest"}}}},"responses":{"201":{"description":"The job was created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TryOnResponse"}}},"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"X-Job-Id":{"description":"The job's id — present as soon as the job exists.","schema":{"type":"string"}},"Location":{"description":"Where to poll the job: `/api/v1/tryon/status/{job_id}`.","schema":{"type":"string"}}}},"200":{"description":"A repeat of an earlier submit with the same `Idempotency-Key`: that job, in its current status.","headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"X-Job-Id":{"description":"The job's id — present as soon as the job exists.","schema":{"type":"string"}},"Location":{"description":"Where to poll the job: `/api/v1/tryon/status/{job_id}`.","schema":{"type":"string"}},"Idempotent-Replayed":{"description":"`true`: the answer of an earlier submit with the same `Idempotency-Key` — nothing new was created or charged.","schema":{"type":"string","enum":["true"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TryOnResponse"}}}},"400":{"description":"Error. `error.code` is one of:\n\n- `INVALID_REQUEST` — an image is a malformed data URI or base64.","x-error-codes":["INVALID_REQUEST"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Error. `error.code` is one of:\n\n- `INVALID_API_KEY` — no `X-API-Key`, an unknown or revoked key, or a test key while test keys are off.\n- `KEY_EXPIRED` — the key has expired.","x-error-codes":["INVALID_API_KEY","KEY_EXPIRED"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Error. `error.code` is one of:\n\n- `KEY_SUSPENDED` — the key is suspended.\n- `CLIENT_SUSPENDED` — the account is suspended.\n- `ORIGIN_REQUIRED` — a publishable key without `Origin`/`Referer` (it works only from a browser page).\n- `KEY_NOT_BOUND` — a live publishable key with no bound domain.\n- `DOMAIN_NOT_ALLOWED` — the page's origin is not the key's domain (or, for a test key, not in its allowed hosts).\n- `WEBHOOK_NOT_ALLOWED` — `webhook_url` with a publishable key — use a secret key from your server.","x-error-codes":["KEY_SUSPENDED","CLIENT_SUSPENDED","ORIGIN_REQUIRED","KEY_NOT_BOUND","DOMAIN_NOT_ALLOWED","WEBHOOK_NOT_ALLOWED"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Error. `error.code` is one of:\n\n- `IDEMPOTENT_IN_PROGRESS` — a submit with this `Idempotency-Key` is still being processed — repeat with the same key after `Retry-After`.","x-error-codes":["IDEMPOTENT_IN_PROGRESS"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before repeating the request. Sent with every 409, 429 and 503 the API answers itself; nginx's own 429 sends `Retry-After: 1`, its own 502/503/504 send none.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Error. `error.code` is one of:\n\n- `PAYLOAD_TOO_LARGE` — the request body is over 12 MB (nginx; no `X-Request-ID`).","x-error-codes":["PAYLOAD_TOO_LARGE"],"headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Error. `error.code` is one of:\n\n- `VALIDATION_ERROR` — the body or `Idempotency-Key` is malformed, `webhook_url` is not a public address, or an image is unsupported, over 10 MB, over 16 megapixels or under 256×256.\n- `CATEGORY_UNSUPPORTED` — the category cannot be tried on now.\n- `PHOTO_UPLOAD_REQUIRED` — the shopper's photo must be an upload (a data URI) with this key, not a URL.","x-error-codes":["VALIDATION_ERROR","CATEGORY_UNSUPPORTED","PHOTO_UPLOAD_REQUIRED"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Error. `error.code` is one of:\n\n- `RATE_LIMITED` — too many submits: 30 a minute per IP address, 10 per 10 minutes per publishable key and IP (60 for a test key), or the key's hourly limit.\n- `LIMIT_EXCEEDED` — the plan's generations for this period are used up (`Retry-After`: until the reset).\n- `DAILY_CAP_REACHED` — the day's cap of the platform or of this key is reached (`Retry-After`: until 00:00 UTC).","x-error-codes":["RATE_LIMITED","LIMIT_EXCEEDED","DAILY_CAP_REACHED"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before repeating the request. Sent with every 409, 429 and 503 the API answers itself; nginx's own 429 sends `Retry-After: 1`, its own 502/503/504 send none.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Error. `error.code` is one of:\n\n- `INTERNAL_ERROR` — final: if the job had been created it is now failed (its quota comes back); a repeat with the same `Idempotency-Key` answers that job — a new try needs a new key.","x-error-codes":["INTERNAL_ERROR"],"headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API is restarting or unreachable (nginx) — retry later.","x-error-codes":["SERVICE_UNAVAILABLE"],"headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Error. `error.code` is one of:\n\n- `SUBMIT_UNCONFIRMED` — the job exists (`X-Job-Id`) but may or may not be queued — poll it or look the attempt up; never resubmit under a new key.\n- `OVERLOADED` — busy, nothing was created or charged — repeat the same request with the same `Idempotency-Key` after `Retry-After`.\n- `SERVICE_UNAVAILABLE` — the API is temporarily unavailable (nginx) — retry later.","x-error-codes":["SUBMIT_UNCONFIRMED","OVERLOADED","SERVICE_UNAVAILABLE"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before repeating the request. Sent with every 409, 429 and 503 the API answers itself; nginx's own 429 sends `Retry-After: 1`, its own 502/503/504 send none.","schema":{"type":"integer","minimum":1}},"X-Job-Id":{"description":"The job's id — present as soon as the job exists.","schema":{"type":"string"}},"Location":{"description":"Where to poll the job: `/api/v1/tryon/status/{job_id}`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"504":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API did not answer in time (nginx).","x-error-codes":["SERVICE_UNAVAILABLE"],"headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, recommended: 22–64 characters of `A-Z a-z 0-9 _ -`, new for every try-on. A repeat with the same key answers the first job (200, `Idempotent-Replayed: true`) — never a second job or charge. Unique per API key and kind of key.","schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{22,64}$"}}]}},"/api/v1/tryon/domain-check":{"get":{"tags":["tryon"],"summary":"Verify domain is whitelisted for this API key","description":"Whether this key may be used on the calling page (`Origin`/`Referer`) and whether its plan still has\ngenerations. A publishable key gets only `limit_exceeded`.","operationId":"domain_check_api_v1_tryon_domain_check_get","responses":{"200":{"description":"The key may be used here.","headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainCheckResponse"}}}},"401":{"description":"Error. `error.code` is one of:\n\n- `INVALID_API_KEY` — no `X-API-Key`, an unknown or revoked key, or a test key while test keys are off.\n- `KEY_EXPIRED` — the key has expired.","headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"x-error-codes":["INVALID_API_KEY","KEY_EXPIRED"]},"403":{"description":"Error. `error.code` is one of:\n\n- `KEY_SUSPENDED` — the key is suspended.\n- `CLIENT_SUSPENDED` — the account is suspended.\n- `ORIGIN_REQUIRED` — a publishable key without `Origin`/`Referer` (it works only from a browser page).\n- `KEY_NOT_BOUND` — a live publishable key with no bound domain.\n- `DOMAIN_NOT_ALLOWED` — the page's origin is not the key's domain (or, for a test key, not in its allowed hosts).","headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"x-error-codes":["KEY_SUSPENDED","CLIENT_SUSPENDED","ORIGIN_REQUIRED","KEY_NOT_BOUND","DOMAIN_NOT_ALLOWED"]},"413":{"description":"Error. `error.code` is one of:\n\n- `PAYLOAD_TOO_LARGE` — the request body is over 12 MB (nginx; no `X-Request-ID`).","headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"x-error-codes":["PAYLOAD_TOO_LARGE"]},"422":{"description":"Error. `error.code` is one of:\n\n- `VALIDATION_ERROR` — a parameter is malformed; `details` names it.","headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"x-error-codes":["VALIDATION_ERROR"]},"429":{"description":"Error. `error.code` is one of:\n\n- `RATE_LIMITED` — nginx's per-IP rate of this path was exceeded (`Retry-After: 1`).","headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before repeating the request. Sent with every 409, 429 and 503 the API answers itself; nginx's own 429 sends `Retry-After: 1`, its own 502/503/504 send none.","schema":{"type":"integer","minimum":1.0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"x-error-codes":["RATE_LIMITED"]},"502":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API is restarting or unreachable (nginx) — retry later.","headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"x-error-codes":["SERVICE_UNAVAILABLE"]},"503":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API is temporarily unavailable (nginx) — retry later.","headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before repeating the request. Sent with every 409, 429 and 503 the API answers itself; nginx's own 429 sends `Retry-After: 1`, its own 502/503/504 send none.","schema":{"type":"integer","minimum":1.0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"x-error-codes":["SERVICE_UNAVAILABLE"]},"504":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API did not answer in time (nginx).","headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"x-error-codes":["SERVICE_UNAVAILABLE"]}},"security":[{"ApiKeyAuth":[]}]}},"/api/v1/tryon/status/{job_id}":{"get":{"tags":["tryon"],"summary":"Check the status of a try-on job","description":"The job's status and progress; `result_url` once it is `completed`, `error_code` and a safe `error_message`\nonce it is `failed` or `interrupted`. Every job is final within 45 minutes. A publishable key sees only its own\njobs, a secret key those of its account; a test key only its own test jobs.","operationId":"get_tryon_status_api_v1_tryon_status__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"The job's status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TryOnStatusResponse"}}},"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}}},"400":{"description":"Error. `error.code` is one of:\n\n- `INVALID_REQUEST` — `job_id` is not a UUID.","x-error-codes":["INVALID_REQUEST"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Error. `error.code` is one of:\n\n- `INVALID_API_KEY` — no `X-API-Key`, an unknown or revoked key, or a test key while test keys are off.\n- `KEY_EXPIRED` — the key has expired.","x-error-codes":["INVALID_API_KEY","KEY_EXPIRED"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Error. `error.code` is one of:\n\n- `KEY_SUSPENDED` — the key is suspended.\n- `CLIENT_SUSPENDED` — the account is suspended.\n- `ORIGIN_REQUIRED` — a publishable key without `Origin`/`Referer` (it works only from a browser page).\n- `KEY_NOT_BOUND` — a live publishable key with no bound domain.\n- `DOMAIN_NOT_ALLOWED` — the page's origin is not the key's domain (or, for a test key, not in its allowed hosts).","x-error-codes":["KEY_SUSPENDED","CLIENT_SUSPENDED","ORIGIN_REQUIRED","KEY_NOT_BOUND","DOMAIN_NOT_ALLOWED"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Error. `error.code` is one of:\n\n- `NOT_FOUND` — no such job for this key.","x-error-codes":["NOT_FOUND"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Error. `error.code` is one of:\n\n- `PAYLOAD_TOO_LARGE` — the request body is over 12 MB (nginx; no `X-Request-ID`).","x-error-codes":["PAYLOAD_TOO_LARGE"],"headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Error. `error.code` is one of:\n\n- `VALIDATION_ERROR` — a parameter is malformed; `details` names it.","x-error-codes":["VALIDATION_ERROR"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Error. `error.code` is one of:\n\n- `RATE_LIMITED` — nginx's per-IP rate of this path was exceeded (`Retry-After: 1`).","x-error-codes":["RATE_LIMITED"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before repeating the request. Sent with every 409, 429 and 503 the API answers itself; nginx's own 429 sends `Retry-After: 1`, its own 502/503/504 send none.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API is restarting or unreachable (nginx) — retry later.","x-error-codes":["SERVICE_UNAVAILABLE"],"headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API is temporarily unavailable (nginx) — retry later.","x-error-codes":["SERVICE_UNAVAILABLE"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before repeating the request. Sent with every 409, 429 and 503 the API answers itself; nginx's own 429 sends `Retry-After: 1`, its own 502/503/504 send none.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"504":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API did not answer in time (nginx).","x-error-codes":["SERVICE_UNAVAILABLE"],"headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/api/v1/tryon/attempts/{idempotency_key}":{"get":{"tags":["tryon"],"summary":"Find the job of a submit by its Idempotency-Key","description":"Find the job a submit created, by the `Idempotency-Key` it carried — for when the answer to the POST was lost.\nScoped to this API key and kind of key. A 404 is final only about 2 minutes after the submit was sent.","operationId":"get_tryon_attempt_api_v1_tryon_attempts__idempotency_key__get","parameters":[{"name":"idempotency_key","in":"path","required":true,"schema":{"type":"string","title":"Idempotency Key"}}],"responses":{"200":{"description":"The job created by that submit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TryOnStatusResponse"}}},"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"X-Job-Id":{"description":"The job's id — present as soon as the job exists.","schema":{"type":"string"}},"Location":{"description":"Where to poll the job: `/api/v1/tryon/status/{job_id}`.","schema":{"type":"string"}}}},"401":{"description":"Error. `error.code` is one of:\n\n- `INVALID_API_KEY` — no `X-API-Key`, an unknown or revoked key, or a test key while test keys are off.\n- `KEY_EXPIRED` — the key has expired.","x-error-codes":["INVALID_API_KEY","KEY_EXPIRED"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Error. `error.code` is one of:\n\n- `KEY_SUSPENDED` — the key is suspended.\n- `CLIENT_SUSPENDED` — the account is suspended.\n- `ORIGIN_REQUIRED` — a publishable key without `Origin`/`Referer` (it works only from a browser page).\n- `KEY_NOT_BOUND` — a live publishable key with no bound domain.\n- `DOMAIN_NOT_ALLOWED` — the page's origin is not the key's domain (or, for a test key, not in its allowed hosts).","x-error-codes":["KEY_SUSPENDED","CLIENT_SUSPENDED","ORIGIN_REQUIRED","KEY_NOT_BOUND","DOMAIN_NOT_ALLOWED"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Error. `error.code` is one of:\n\n- `NOT_FOUND` — no try-on was created with this key — not final until about 2 minutes after the submit was sent.","x-error-codes":["NOT_FOUND"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Error. `error.code` is one of:\n\n- `IDEMPOTENT_IN_PROGRESS` — the submit is still being processed — ask again after `Retry-After`.","x-error-codes":["IDEMPOTENT_IN_PROGRESS"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before repeating the request. Sent with every 409, 429 and 503 the API answers itself; nginx's own 429 sends `Retry-After: 1`, its own 502/503/504 send none.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Error. `error.code` is one of:\n\n- `PAYLOAD_TOO_LARGE` — the request body is over 12 MB (nginx; no `X-Request-ID`).","x-error-codes":["PAYLOAD_TOO_LARGE"],"headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Error. `error.code` is one of:\n\n- `VALIDATION_ERROR` — the key is not 22–64 characters of `A-Z a-z 0-9 _ -`.","x-error-codes":["VALIDATION_ERROR"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Error. `error.code` is one of:\n\n- `RATE_LIMITED` — nginx's per-IP rate of this path was exceeded (`Retry-After: 1`).","x-error-codes":["RATE_LIMITED"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before repeating the request. Sent with every 409, 429 and 503 the API answers itself; nginx's own 429 sends `Retry-After: 1`, its own 502/503/504 send none.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API is restarting or unreachable (nginx) — retry later.","x-error-codes":["SERVICE_UNAVAILABLE"],"headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Error. `error.code` is one of:\n\n- `ATTEMPT_LOOKUP_UNAVAILABLE` — cannot tell now whether the submit is running — ask again after `Retry-After`.\n- `SERVICE_UNAVAILABLE` — the API is temporarily unavailable (nginx) — retry later.","x-error-codes":["ATTEMPT_LOOKUP_UNAVAILABLE","SERVICE_UNAVAILABLE"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before repeating the request. Sent with every 409, 429 and 503 the API answers itself; nginx's own 429 sends `Retry-After: 1`, its own 502/503/504 send none.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"504":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API did not answer in time (nginx).","x-error-codes":["SERVICE_UNAVAILABLE"],"headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/api/v1/tryon/result/{job_id}":{"get":{"tags":["tryon"],"summary":"Fetch a completed job's result image bytes","description":"The bytes of a completed job's result image — for a gallery that keeps its own copy (the media host sends no\nCORS headers). Same scope as the status; every «cannot» (no such job, not completed, file gone) is a 404.","operationId":"get_tryon_result_api_v1_tryon_result__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"The result image.","content":{"application/json":{"schema":{}},"image/png":{},"image/jpeg":{},"image/webp":{}},"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}}},"400":{"description":"Error. `error.code` is one of:\n\n- `INVALID_REQUEST` — `job_id` is not a UUID.","x-error-codes":["INVALID_REQUEST"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Error. `error.code` is one of:\n\n- `INVALID_API_KEY` — no `X-API-Key`, an unknown or revoked key, or a test key while test keys are off.\n- `KEY_EXPIRED` — the key has expired.","x-error-codes":["INVALID_API_KEY","KEY_EXPIRED"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Error. `error.code` is one of:\n\n- `KEY_SUSPENDED` — the key is suspended.\n- `CLIENT_SUSPENDED` — the account is suspended.\n- `ORIGIN_REQUIRED` — a publishable key without `Origin`/`Referer` (it works only from a browser page).\n- `KEY_NOT_BOUND` — a live publishable key with no bound domain.\n- `DOMAIN_NOT_ALLOWED` — the page's origin is not the key's domain (or, for a test key, not in its allowed hosts).","x-error-codes":["KEY_SUSPENDED","CLIENT_SUSPENDED","ORIGIN_REQUIRED","KEY_NOT_BOUND","DOMAIN_NOT_ALLOWED"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Error. `error.code` is one of:\n\n- `NOT_FOUND` — no result for this key: no such job, not completed yet, or the file is gone.","x-error-codes":["NOT_FOUND"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Error. `error.code` is one of:\n\n- `PAYLOAD_TOO_LARGE` — the request body is over 12 MB (nginx; no `X-Request-ID`).","x-error-codes":["PAYLOAD_TOO_LARGE"],"headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Error. `error.code` is one of:\n\n- `VALIDATION_ERROR` — a parameter is malformed; `details` names it.","x-error-codes":["VALIDATION_ERROR"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Error. `error.code` is one of:\n\n- `RATE_LIMITED` — nginx's per-IP rate of this path was exceeded (`Retry-After: 1`).","x-error-codes":["RATE_LIMITED"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before repeating the request. Sent with every 409, 429 and 503 the API answers itself; nginx's own 429 sends `Retry-After: 1`, its own 502/503/504 send none.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API is restarting or unreachable (nginx) — retry later.","x-error-codes":["SERVICE_UNAVAILABLE"],"headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API is temporarily unavailable (nginx) — retry later.","x-error-codes":["SERVICE_UNAVAILABLE"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before repeating the request. Sent with every 409, 429 and 503 the API answers itself; nginx's own 429 sends `Retry-After: 1`, its own 502/503/504 send none.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"504":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API did not answer in time (nginx).","x-error-codes":["SERVICE_UNAVAILABLE"],"headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/api/v1/tryon/jobs":{"get":{"tags":["tryon"],"summary":"List the jobs of the calling secret API key, newest first (keyset pages)","description":"The jobs of the calling key, newest first: a live secret key lists its live jobs, a test key its test jobs —\nnever another key's. Needs the secret key (a publishable key is refused). Filter by `since` (ISO 8601, inclusive),\n`status` and `product_url`; page with `cursor` (the `next_cursor` of the previous page) and `limit` (1–100, 50 by\ndefault). At most 60 requests a minute per key. Use it to reconcile after missed webhooks.","operationId":"list_tryon_jobs_api_v1_tryon_jobs_get","parameters":[{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Since"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"enum":["pending","processing","completed","failed","interrupted"],"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"product_url","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"maxLength":2048,"title":"Product Url"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"A page of the key's jobs, newest first; `next_cursor` is null on the last page.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicJobList"}}},"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}}},"401":{"description":"Error. `error.code` is one of:\n\n- `INVALID_API_KEY` — no `X-API-Key`, an unknown or revoked key, or a test key while test keys are off.\n- `KEY_EXPIRED` — the key has expired.","x-error-codes":["INVALID_API_KEY","KEY_EXPIRED"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Error. `error.code` is one of:\n\n- `KEY_SUSPENDED` — the key is suspended.\n- `CLIENT_SUSPENDED` — the account is suspended.\n- `ORIGIN_REQUIRED` — a publishable key without `Origin`/`Referer` (it works only from a browser page).\n- `KEY_NOT_BOUND` — a live publishable key with no bound domain.\n- `DOMAIN_NOT_ALLOWED` — the page's origin is not the key's domain (or, for a test key, not in its allowed hosts).\n- `SECRET_KEY_REQUIRED` — a publishable key — this call needs the secret key, from a server.","x-error-codes":["KEY_SUSPENDED","CLIENT_SUSPENDED","ORIGIN_REQUIRED","KEY_NOT_BOUND","DOMAIN_NOT_ALLOWED","SECRET_KEY_REQUIRED"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Error. `error.code` is one of:\n\n- `PAYLOAD_TOO_LARGE` — the request body is over 12 MB (nginx; no `X-Request-ID`).","x-error-codes":["PAYLOAD_TOO_LARGE"],"headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Error. `error.code` is one of:\n\n- `VALIDATION_ERROR` — `since` is not an ISO 8601 date-time, `cursor` is not one this endpoint returned, `limit` is outside 1–100, or `status` is not a job status.","x-error-codes":["VALIDATION_ERROR"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Error. `error.code` is one of:\n\n- `RATE_LIMITED` — more than 60 list requests a minute for this key (`Retry-After`).","x-error-codes":["RATE_LIMITED"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before repeating the request. Sent with every 409, 429 and 503 the API answers itself; nginx's own 429 sends `Retry-After: 1`, its own 502/503/504 send none.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API is restarting or unreachable (nginx) — retry later.","x-error-codes":["SERVICE_UNAVAILABLE"],"headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API is temporarily unavailable (nginx) — retry later.","x-error-codes":["SERVICE_UNAVAILABLE"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before repeating the request. Sent with every 409, 429 and 503 the API answers itself; nginx's own 429 sends `Retry-After: 1`, its own 502/503/504 send none.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"504":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API did not answer in time (nginx).","x-error-codes":["SERVICE_UNAVAILABLE"],"headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/api/v1/tryon/capabilities":{"get":{"tags":["tryon"],"summary":"What the model can try on — categories, labels, unsupported items, photo tips","description":"What the service can try on: the categories (labels and examples in English and Russian), `auto`, the items it\ncannot try on and photo tips for the shopper. No key; a cache may keep it 5 minutes.","operationId":"tryon_capabilities_api_v1_tryon_capabilities_get","responses":{"200":{"description":"The capabilities document.","headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"Cache-Control":{"description":"`public, max-age=300`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CapabilitiesDocument"}}}},"413":{"description":"Error. `error.code` is one of:\n\n- `PAYLOAD_TOO_LARGE` — the request body is over 12 MB (nginx; no `X-Request-ID`).","headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"x-error-codes":["PAYLOAD_TOO_LARGE"]},"429":{"description":"Error. `error.code` is one of:\n\n- `RATE_LIMITED` — nginx's per-IP rate of this path was exceeded (`Retry-After: 1`).","headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before repeating the request. Sent with every 409, 429 and 503 the API answers itself; nginx's own 429 sends `Retry-After: 1`, its own 502/503/504 send none.","schema":{"type":"integer","minimum":1.0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"x-error-codes":["RATE_LIMITED"]},"502":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API is restarting or unreachable (nginx) — retry later.","headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"x-error-codes":["SERVICE_UNAVAILABLE"]},"503":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API is temporarily unavailable (nginx) — retry later.","headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before repeating the request. Sent with every 409, 429 and 503 the API answers itself; nginx's own 429 sends `Retry-After: 1`, its own 502/503/504 send none.","schema":{"type":"integer","minimum":1.0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"x-error-codes":["SERVICE_UNAVAILABLE"]},"504":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API did not answer in time (nginx).","headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"x-error-codes":["SERVICE_UNAVAILABLE"]}}}},"/api/v1/tryon/integration":{"get":{"tags":["tryon"],"summary":"Verify a secret API key and return what a plugin needs to connect a site","description":"Secret key only, from a server: verifies the key, tells whether `site_url` is the key's domain and returns what\na plugin needs — the publishable key, the widget's script URL, the plan's usage and the capabilities.","operationId":"tryon_integration_api_v1_tryon_integration_get","parameters":[{"name":"site_url","in":"query","required":true,"schema":{"type":"string","title":"Site Url"}}],"responses":{"200":{"description":"What a plugin needs to connect this site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntegrationResponse"}}},"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}}},"401":{"description":"Error. `error.code` is one of:\n\n- `INVALID_API_KEY` — no `X-API-Key`, an unknown or revoked key, or a test key while test keys are off.\n- `KEY_EXPIRED` — the key has expired.","x-error-codes":["INVALID_API_KEY","KEY_EXPIRED"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Error. `error.code` is one of:\n\n- `KEY_SUSPENDED` — the key is suspended.\n- `CLIENT_SUSPENDED` — the account is suspended.\n- `ORIGIN_REQUIRED` — a publishable key without `Origin`/`Referer` (it works only from a browser page).\n- `KEY_NOT_BOUND` — a live publishable key with no bound domain.\n- `DOMAIN_NOT_ALLOWED` — the page's origin is not the key's domain (or, for a test key, not in its allowed hosts).\n- `SECRET_KEY_REQUIRED` — a publishable key — this call needs the secret key, from a server.","x-error-codes":["KEY_SUSPENDED","CLIENT_SUSPENDED","ORIGIN_REQUIRED","KEY_NOT_BOUND","DOMAIN_NOT_ALLOWED","SECRET_KEY_REQUIRED"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Error. `error.code` is one of:\n\n- `PAYLOAD_TOO_LARGE` — the request body is over 12 MB (nginx; no `X-Request-ID`).","x-error-codes":["PAYLOAD_TOO_LARGE"],"headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Error. `error.code` is one of:\n\n- `VALIDATION_ERROR` — `site_url` is missing or not an http(s) URL.","x-error-codes":["VALIDATION_ERROR"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Error. `error.code` is one of:\n\n- `RATE_LIMITED` — nginx's per-IP rate of this path was exceeded (`Retry-After: 1`).","x-error-codes":["RATE_LIMITED"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before repeating the request. Sent with every 409, 429 and 503 the API answers itself; nginx's own 429 sends `Retry-After: 1`, its own 502/503/504 send none.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API is restarting or unreachable (nginx) — retry later.","x-error-codes":["SERVICE_UNAVAILABLE"],"headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API is temporarily unavailable (nginx) — retry later.","x-error-codes":["SERVICE_UNAVAILABLE"],"headers":{"X-Request-ID":{"description":"The id of this request — quote it to support. Absent on nginx's own answers.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before repeating the request. Sent with every 409, 429 and 503 the API answers itself; nginx's own 429 sends `Retry-After: 1`, its own 502/503/504 send none.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"504":{"description":"Error. `error.code` is one of:\n\n- `SERVICE_UNAVAILABLE` — the API did not answer in time (nginx).","x-error-codes":["SERVICE_UNAVAILABLE"],"headers":{},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"ApiKeyAuth":[]}]}}},"webhooks":{"job.completed":{"post":{"summary":"A job completed","description":"Sent when a job ends `completed`: `result_url` is set.\n\nDelivery is at least once. Attempt 1 goes out as soon as the job ends (normally within about 10 s); a failed attempt is retried after 1 min, 5 min, 30 min and 2 h. The endpoint's `retry_count` (1–5, default 3) is the number of attempts in all. Retried: a network error, a timeout (10 s for the whole request), any 5xx, 408 and 429; a `Retry-After` on 429 or 503 is honoured when it is longer, up to 6 h; any other 4xx is final. The same `delivery_id` is sent on every attempt — deduplicate by it. Webhooks are best effort: reconcile by polling `GET /api/v1/tryon/status/{job_id}`.\n\nVerify `X-TryOn-Signature-V2` over the raw request body exactly as received: `sha256=` + hex HMAC-SHA256(endpoint secret, `X-TryOn-Timestamp` + \".\" + body). Reject a timestamp more than 5 minutes away from your clock. `X-TryOn-Signature` (HMAC of the body alone) is kept for older receivers.","operationId":"webhook_job_completed","parameters":[{"description":"The event name: `job.completed`.","required":true,"schema":{"type":"string","title":"X-Tryon-Event","description":"The event name: `job.completed`."},"name":"X-TryOn-Event","in":"header"},{"description":"Unix seconds of this attempt.","required":true,"schema":{"type":"string","title":"X-Tryon-Timestamp","description":"Unix seconds of this attempt."},"name":"X-TryOn-Timestamp","in":"header"},{"description":"`sha256=` + hex HMAC-SHA256(secret, `<X-TryOn-Timestamp>.<raw body>`) — verify this one.","required":true,"schema":{"type":"string","title":"X-Tryon-Signature-V2","description":"`sha256=` + hex HMAC-SHA256(secret, `<X-TryOn-Timestamp>.<raw body>`) — verify this one."},"name":"X-TryOn-Signature-V2","in":"header"},{"description":"`sha256=` + hex HMAC-SHA256(secret, raw body) — legacy.","required":true,"schema":{"type":"string","title":"X-Tryon-Signature","description":"`sha256=` + hex HMAC-SHA256(secret, raw body) — legacy."},"name":"X-TryOn-Signature","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobEvent"}}},"required":true},"responses":{"200":{"description":"Any 2xx acknowledges the delivery; answer within 10 s.","content":{"application/json":{"schema":{}}}},"default":{"description":"Any other answer is a failed attempt: 5xx, 408 and 429 are retried, other 4xx are final."}}}},"job.failed":{"post":{"summary":"A job failed","description":"Sent when a job ends `failed` or `interrupted`: `error_code` and `error_message` are set.\n\nDelivery is at least once. Attempt 1 goes out as soon as the job ends (normally within about 10 s); a failed attempt is retried after 1 min, 5 min, 30 min and 2 h. The endpoint's `retry_count` (1–5, default 3) is the number of attempts in all. Retried: a network error, a timeout (10 s for the whole request), any 5xx, 408 and 429; a `Retry-After` on 429 or 503 is honoured when it is longer, up to 6 h; any other 4xx is final. The same `delivery_id` is sent on every attempt — deduplicate by it. Webhooks are best effort: reconcile by polling `GET /api/v1/tryon/status/{job_id}`.\n\nVerify `X-TryOn-Signature-V2` over the raw request body exactly as received: `sha256=` + hex HMAC-SHA256(endpoint secret, `X-TryOn-Timestamp` + \".\" + body). Reject a timestamp more than 5 minutes away from your clock. `X-TryOn-Signature` (HMAC of the body alone) is kept for older receivers.","operationId":"webhook_job_failed","parameters":[{"description":"The event name: `job.failed`.","required":true,"schema":{"type":"string","title":"X-Tryon-Event","description":"The event name: `job.failed`."},"name":"X-TryOn-Event","in":"header"},{"description":"Unix seconds of this attempt.","required":true,"schema":{"type":"string","title":"X-Tryon-Timestamp","description":"Unix seconds of this attempt."},"name":"X-TryOn-Timestamp","in":"header"},{"description":"`sha256=` + hex HMAC-SHA256(secret, `<X-TryOn-Timestamp>.<raw body>`) — verify this one.","required":true,"schema":{"type":"string","title":"X-Tryon-Signature-V2","description":"`sha256=` + hex HMAC-SHA256(secret, `<X-TryOn-Timestamp>.<raw body>`) — verify this one."},"name":"X-TryOn-Signature-V2","in":"header"},{"description":"`sha256=` + hex HMAC-SHA256(secret, raw body) — legacy.","required":true,"schema":{"type":"string","title":"X-Tryon-Signature","description":"`sha256=` + hex HMAC-SHA256(secret, raw body) — legacy."},"name":"X-TryOn-Signature","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobEvent"}}},"required":true},"responses":{"200":{"description":"Any 2xx acknowledges the delivery; answer within 10 s.","content":{"application/json":{"schema":{}}}},"default":{"description":"Any other answer is a failed attempt: 5xx, 408 and 429 are retried, other 4xx are final."}}}}},"components":{"schemas":{"AutoCategory":{"properties":{"id":{"type":"string","title":"Id","description":"Always `auto`: the model decides from the garment photo."},"label":{"$ref":"#/components/schemas/LocalizedText"},"available":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Available","description":"Present and `false` only when `auto` cannot be served now."}},"additionalProperties":true,"type":"object","required":["id","label"],"title":"AutoCategory"},"CapabilitiesDocument":{"properties":{"model":{"$ref":"#/components/schemas/PublicModel"},"categories":{"items":{"$ref":"#/components/schemas/CapabilityCategory"},"type":"array","title":"Categories"},"auto":{"$ref":"#/components/schemas/AutoCategory"},"unsupported":{"$ref":"#/components/schemas/LocalizedList","description":"Items the model cannot try on, for the shopper."},"photo_tips":{"$ref":"#/components/schemas/LocalizedList","description":"How to take a photo that works, for the shopper."},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At","description":"ISO 8601: when the category list last changed."}},"additionalProperties":true,"type":"object","required":["model","categories","auto","unsupported","photo_tips"],"title":"CapabilitiesDocument","description":"What the service can try on: the categories, the `auto` choice, what it cannot try on, and photo tips."},"CapabilityCategory":{"properties":{"id":{"type":"string","title":"Id","description":"The platform category id to send as `category` in POST /api/v1/tryon."},"label":{"$ref":"#/components/schemas/LocalizedText"},"examples":{"$ref":"#/components/schemas/LocalizedText"},"new":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"New","description":"`true` while the category is newly published."},"keywords":{"anyOf":[{"$ref":"#/components/schemas/LocalizedList"},{"type":"null"}],"description":"Hints for mapping a shop's own categories to this one."}},"additionalProperties":true,"type":"object","required":["id","label","examples"],"title":"CapabilityCategory"},"DomainCheckResponse":{"properties":{"allowed":{"type":"boolean","title":"Allowed"},"domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain"},"client_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Name"},"plan_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plan Id"},"limit_exceeded":{"type":"boolean","title":"Limit Exceeded","default":false},"limit_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Limit Remaining"},"mode":{"type":"string","title":"Mode","default":"live"}},"type":"object","required":["allowed"],"title":"DomainCheckResponse","description":"Response from GET /api/v1/tryon/domain-check."},"ErrorBody":{"properties":{"code":{"type":"string","title":"Code","description":"Machine-readable code — decide by it, never by `message`."},"message":{"type":"string","title":"Message","description":"Human-readable text; may change at any time."},"details":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Details","description":"Only on a 422 `VALIDATION_ERROR`: one item per invalid field (`loc`, `msg`, `type`)."}},"type":"object","required":["code","message"],"title":"ErrorBody"},"ErrorResponse":{"properties":{"error":{"$ref":"#/components/schemas/ErrorBody"}},"type":"object","required":["error"],"title":"ErrorResponse","description":"Every error answer of the API, nginx's own answers included."},"IntegrationClientInfo":{"properties":{"name":{"type":"string","title":"Name"}},"type":"object","required":["name"],"title":"IntegrationClientInfo"},"IntegrationKeyInfo":{"properties":{"domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain"},"publishable_key":{"type":"string","title":"Publishable Key"},"mode":{"type":"string","title":"Mode","default":"live"},"last_integration":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Integration"}},"type":"object","required":["publishable_key"],"title":"IntegrationKeyInfo"},"IntegrationManifestEntry":{"properties":{"latest_version":{"type":"string","title":"Latest Version"},"download_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Download Url"},"docs_url":{"type":"string","title":"Docs Url"}},"type":"object","required":["latest_version","docs_url"],"title":"IntegrationManifestEntry","description":"One entry of the integrations manifest (U6): the latest release of a plugin/module, where to get it (null when we\nhand the archive over ourselves) and its documentation."},"IntegrationPlanInfo":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"generations_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Generations Limit"},"generations_used":{"type":"integer","title":"Generations Used"},"generations_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Generations Remaining"},"resets_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Resets At"}},"type":"object","required":["generations_used"],"title":"IntegrationPlanInfo"},"IntegrationPluginInfo":{"properties":{"latest_version":{"type":"string","title":"Latest Version"},"download_url":{"type":"string","title":"Download Url"}},"type":"object","required":["latest_version","download_url"],"title":"IntegrationPluginInfo"},"IntegrationResponse":{"properties":{"client":{"$ref":"#/components/schemas/IntegrationClientInfo"},"key":{"$ref":"#/components/schemas/IntegrationKeyInfo"},"site":{"$ref":"#/components/schemas/IntegrationSiteInfo"},"plan":{"$ref":"#/components/schemas/IntegrationPlanInfo"},"widget":{"$ref":"#/components/schemas/IntegrationWidgetInfo"},"plugin":{"$ref":"#/components/schemas/IntegrationPluginInfo"},"integrations":{"additionalProperties":{"$ref":"#/components/schemas/IntegrationManifestEntry"},"type":"object","title":"Integrations"},"capabilities":{"additionalProperties":true,"type":"object","title":"Capabilities"}},"type":"object","required":["client","key","site","plan","widget","plugin","integrations","capabilities"],"title":"IntegrationResponse"},"IntegrationSiteInfo":{"properties":{"url":{"type":"string","title":"Url"},"host":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Host"},"allowed":{"type":"boolean","title":"Allowed"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"}},"type":"object","required":["url","allowed"],"title":"IntegrationSiteInfo"},"IntegrationWidgetInfo":{"properties":{"script_url":{"type":"string","title":"Script Url"},"api_url":{"type":"string","title":"Api Url"}},"type":"object","required":["script_url","api_url"],"title":"IntegrationWidgetInfo"},"JobEvent":{"properties":{"event":{"type":"string","enum":["job.completed","job.failed"],"title":"Event"},"job_id":{"type":"string","title":"Job Id","description":"The job's id (UUID)."},"status":{"type":"string","enum":["completed","failed","interrupted"],"title":"Status"},"result_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Result Url","description":"The result image; `null` unless `status` is `completed`."},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code","description":"`GENERATION_FAILED`, `INVALID_IMAGE`, `CONTENT_BLOCKED`, `TIMEOUT` or `INTERRUPTED`; `null` on success."},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message","description":"A safe text for the error code; never the provider's raw error."},"processing_time_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Processing Time Ms"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"The platform category the job ran with."},"product_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Title"},"product_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Url"},"integration":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Integration","description":"The `name/version` label the submit carried, if any."},"api_key_id":{"type":"string","title":"Api Key Id","description":"The API key the job was submitted with."},"key_kind":{"anyOf":[{"type":"string","enum":["secret","publishable"]},{"type":"null"}],"title":"Key Kind","description":"Which key of that row submitted it; `null` when the submit had no `Idempotency-Key`."},"idempotency_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency Key","description":"The submit's `Idempotency-Key`; unique only together with `api_key_id` and `key_kind`."},"test":{"type":"boolean","title":"Test","description":"`true` for a job of a test key (sent only to test endpoints)."},"created_at":{"type":"string","format":"date-time","title":"Created At"},"occurred_at":{"type":"string","format":"date-time","title":"Occurred At","description":"When the job reached this state."},"delivery_id":{"type":"string","title":"Delivery Id","description":"Stable across every attempt of this delivery — deduplicate by it."}},"type":"object","required":["event","job_id","status","result_url","error_code","error_message","processing_time_ms","category","product_title","product_url","integration","api_key_id","key_kind","idempotency_key","test","created_at","occurred_at","delivery_id"],"title":"JobEvent","description":"The body of `job.completed` and `job.failed` — one shape for both events."},"LocalizedList":{"properties":{"en":{"items":{"type":"string"},"type":"array","title":"En"},"ru":{"items":{"type":"string"},"type":"array","title":"Ru"}},"additionalProperties":true,"type":"object","required":["en","ru"],"title":"LocalizedList"},"LocalizedText":{"properties":{"en":{"type":"string","title":"En"},"ru":{"type":"string","title":"Ru"}},"additionalProperties":true,"type":"object","required":["en","ru"],"title":"LocalizedText"},"PublicJobItem":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"processing_time_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Processing Time Ms"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"product_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Title"},"product_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Url"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code"},"result_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Result Url"},"integration":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Integration"},"test":{"type":"boolean","title":"Test"}},"type":"object","required":["job_id","status","created_at","test"],"title":"PublicJobItem"},"PublicJobList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/PublicJobItem"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"PublicJobList"},"PublicModel":{"properties":{"id":{"type":"string","title":"Id","description":"A stable public id of the try-on model; it does not follow the provider."},"name":{"type":"string","title":"Name"}},"additionalProperties":true,"type":"object","required":["id","name"],"title":"PublicModel"},"TryOnRequest":{"properties":{"model_image":{"type":"string","maxLength":14000000,"minLength":1,"title":"Model Image","description":"Base64-encoded data URI (data:image/png;base64,...) or image URL of the person's photo (max 10 MB binary)","example":"data:image/jpeg;base64,/9j/4AAQSkZJRg..."},"garment_image":{"type":"string","maxLength":14000000,"minLength":1,"title":"Garment Image","description":"Base64-encoded data URI or image URL of the garment to try on (max 10 MB binary)","example":"data:image/png;base64,iVBORw0KGgo..."},"category":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,31}$","title":"Category","description":"Platform category id from GET /api/v1/tryon/capabilities (e.g. tops, outerwear, bottoms, one-pieces) or auto; legacy alias dresses → one-pieces. An id no model covers gets 422 CATEGORY_UNSUPPORTED","default":"tops","example":"tops"},"mode":{"type":"string","enum":["balanced","quality"],"title":"Mode","description":"Quality mode: quality (default, higher fidelity) or balanced (faster)","default":"quality","example":"quality"},"webhook_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Url","description":"Optional URL to notify when the job completes or fails","example":"https://example.com/webhooks/tryon"},"product_title":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Product Title","description":"Optional name of the product being tried on, shown next to the job in the admin and the client portal. Up to 1000 characters are accepted (422 above); control characters and most invisible ones are removed (zero-width joiners, direction marks and tag characters that real text needs are kept), whitespace is collapsed and the text is cut to 200 characters. Nothing visible left, or a value that is not a string -> null","example":"Linen shirt"},"product_url":{"anyOf":[{"type":"string","maxLength":4096},{"type":"null"}],"title":"Product Url","description":"Optional link to the product page. Up to 4096 characters are accepted (422 above); stored only if it is a plain http(s) URL with a plain host name (no IP literal), no credentials, no whitespace or control characters, none of \\ \" < > ` { } | ^ and at most 2048 characters — otherwise it is dropped (null) and the try-on goes ahead. With a publishable key the link must also be on the key's bound domain (the domain itself, www. or a subdomain), else it is dropped. A value that is not a string is dropped too","example":"https://shop.example/p/linen-shirt"},"integration":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Integration","description":"Optional label of the plugin or module that sends the try-on, `name/version` (e.g. `woocommerce/1.4.0`, `bitrix/0.1.0`, `widget/1.0.0`): a lowercase name of up to 32 characters, `/`, a version of up to 32 of `0-9A-Za-z.+-`, 64 in all. Stored on the job and shown in the admin. A value that does not fit — or is not a string — is dropped (null) and the try-on goes ahead; it is never a 422","example":"woocommerce/1.4.0"},"test_outcome":{"type":"string","enum":["completed","failed"],"title":"Test Outcome","description":"Test keys only: how the simulated try-on ends, `completed` (default) or `failed`. Any other value is ignored (`completed`) and a live key ignores the field altogether - it is never a 422","default":"completed","example":"completed"}},"type":"object","required":["model_image","garment_image"],"title":"TryOnRequest"},"TryOnResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","title":"Status"},"poll_url":{"type":"string","title":"Poll Url"},"request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request Id"},"estimated_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Estimated Seconds"}},"type":"object","required":["job_id","status","poll_url"],"title":"TryOnResponse"},"TryOnStatusResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","title":"Status"},"progress":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Progress"},"result_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Result Url"},"processing_time_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Processing Time Ms"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code"},"cost_credits":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cost Credits"}},"type":"object","required":["job_id","status"],"title":"TryOnStatusResponse"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"}}}}