Skip to content

Troubleshooting

Organized by what you’re seeing, not by what’s wrong. Each entry says how to confirm the cause before you change anything.

The key is missing, malformed, or revoked.

  • The header is Authorization: Bearer tryon_sk_... — the Bearer prefix is required.
  • Keys are shown once at creation. If you didn’t copy it, you can’t recover it; create a new one in the panel under Settings → API Keys and revoke the old one.
  • A revoked key returns 401, not 403.

See Authentication for the key format and rotation.

404 not_found on a product you know exists

Section titled “404 not_found on a product you know exists”

Two different situations return the same response, deliberately.

  • Wrong id type. REST paths take the TryOn id (a UUID). They do not accept external_id. If all you have is your own SKU, look it up first with GET /v1/products?external_id=SKU-1234 — see Using your own IDs.
  • Another shop’s product. Every request is scoped to the shop that owns the key, and a product belonging to a different shop returns 404 rather than 403. That is intentional: it never confirms whether a resource exists elsewhere. See Cross-shop access.

Processing normally finishes in under a minute. After a few minutes, assume failure rather than slowness.

  1. Check images_failed from the POST /v1/products response. A URL that timed out, wasn’t publicly reachable, wasn’t a JPEG/PNG/WebP/GIF, or exceeded 15 MB is reported there and is never silently retried.
  2. Check the product’s images array. If it’s empty, every URL in the batch failed and there is nothing to process.
  3. Confirm the URL is reachable from the public internet. A URL your own servers can fetch but the public internet can’t — localhost, a private network address, anything behind auth — fails as unreachable_url.

Images lists every per-image code and what it means.

Enabling requires a category (set at create time, and it can’t be left unset later) and a mode.

The common case is mode: realtime, which additionally requires an active 3D asset and returns 400 validation_error with “Active 3D asset required for real-time mode” when none is usable. 3D assets are managed in the panel — this API can’t attach one. mode: swap is the only mode you can drive end-to-end through the API.

Also note that changing mode on a product that already has try-on enabled turns it off again; send a fresh tryon_enabled: true afterward. See Product lifecycle.

Work down this list in order:

  • Both gates satisfied? tryon_status must be "active" and swap_ready must be true. A product can be active with swap_ready: false — the button opens an empty experience. See tryon_status vs. swap_ready.
  • Is the bootstrap script actually on the page? The button is inert on its own; it only binds because the script is present.
  • Does the script’s productId match the button’s data-tryon-product-id? They have to be the same product.
  • Is shop_slug correct? Find it in the panel under Settings → Shop.

One thing that catches people: the bootstrap script is served from /api/v1/tryon/scripts/embed/bootstrap.js, not from the /v1/... public API namespace the rest of these docs use. Same host, different path — copy the URL from Storefront integration exactly rather than reconstructing it from the REST base.

Only one product opens on a page with several

Section titled “Only one product opens on a page with several”

Expected, and it’s a hard limit rather than a bug. Every bootstrap script defines the same window.openTryOn global, so a second script on the same page overwrites the first instead of adding a product.

For grids, quick-view cards, and bundle pages, link each product to its own product page. See The two snippets.

The limit is 120 requests per minute per API key. The response carries a Retry-After header in seconds — wait at least that long before retrying, and space out a catalog import rather than firing it all at once. See Rate limits.

Open the support chat in the merchant panel — the bubble in the bottom corner reaches the team directly. Include the product id, the endpoint you called, and the code from the error envelope; all three make it answerable on the first reply.