Custom UI workflow
Use this sequence when building your own interface. Headless API does not require Storefront tokens in Antla. The downloadable HTTP client implements the transport, bounded polling and error handling used by the examples.
Connect Headless API first. If you also connected Headless SDK, you may call GET /context. API-only shops skip that step.
1. Load the product
Section titled “1. Load the product”Use your existing catalog data for the connected store. You need the Shopify product ID, a variant ID, at least one product or variant image for Antla to pick as the garment, and tags matching antla_in_funnel_<positive integer>.
If Headless SDK is connected, you may instead call GET /context and query Shopify Storefront:
query AntlaProduct($productId: ID!) { product(id: $productId) { id tags images(first: 1) { nodes { url } } variants(first: 100) { nodes { id title availableForSale image { url } } } }}Handle Shopify’s HTTP errors and GraphQL errors. A null product can mean the product is not visible to that storefront. Validate the selected variant belongs to the product. Paginate variants if your custom interface supports products with larger variant sets.
Generation does not reject a sold-out variant. If you sell through Shopify, check availableForSale before add-to-cart. Antla verifies tags, variant membership, and a usable image through the Admin API. It does not treat Storefront publication as a generation gate.
You may use your existing catalog data instead, provided it belongs to the connected store and you keep tags and variants current.
2. Resolve a funnel
Section titled “2. Resolve a funnel”Parse tags matching antla_in_funnel_<positive integer>. With multiple matches, preserve your chosen preference order and call GET /funnel.
const funnelIds = product.tags.flatMap((tag) => { const match = /^antla_in_funnel_([1-9][0-9]*)$/.exec(tag); return match ? [Number(match[1])] : [];});if (!funnelIds.length) { // Hide or disable try-on for this product.}The funnel endpoint validates candidates against the connected store and selects the first valid match. It does not receive a product ID and does not verify product membership. The generation endpoint checks the product’s exact funnel tag.
You can skip the lookup if you already have a valid funnel ID assigned to the product. You cannot omit funnelId when starting a generation.
3. Prepare the shopper photo
Section titled “3. Prepare the shopper photo”Either use a publicly retrievable photo URL you control or call POST /uploads. When using an Antla upload target, PUT the image bytes with the selected image MIME type to uploadUrl, then use imageUrl.
Do not send an SDK key to the upload target. Do not POST base64, multipart data, a local file path or a blob: URL to /generations.
Use image validation guidance before uploading.
4. Resolve visitor identity and email
Section titled “4. Resolve visitor identity and email”Optionally call GET /requests-left with a saved uniqueUserId. Save a non-null returned ID under this merchant and visitor. A value of zero means collect email; minus one means the email gate does not apply.
If email is required, call POST /customer-register first or provide customerEmail with the generation. Regardless of the pre-check, handle EMAIL_REQUIRED on the generation response.
For generation-linked Klaviyo tracking, send customerEmail on that generation even when the visitor is already registered. An earlier registration does not attach an email to a later generation.
5. Start exactly one attempt
Section titled “5. Start exactly one attempt”curl --fail-with-body \ -X POST "https://app-antla.com/api/headless/generations" \ -H "X-Antla-SDK-Key: YOUR_ANTLA_SDK_KEY" \ -H "Content-Type: application/json" \ --data '{ "funnelId": 123, "productId": "gid://shopify/Product/123", "variantId": "gid://shopify/ProductVariant/456", "imageUrl": "https://YOUR_PUBLIC_IMAGE_HOST/customer-photo.jpg", "uniqueUserId": "SAVED_VISITOR_ID" }'Replace the sample values. Omit uniqueUserId if none exists; do not send the placeholder literally. Add customerEmail and moreLooksConsent only when you have the shopper’s corresponding input.
Save data.jobId as soon as the response arrives. The documented successful status is HTTP 200, not 201 or 202. etaSeconds is an estimate, not a service-level guarantee.
Disable duplicate submission while this attempt is pending. There is no documented idempotency key. A network failure or server error after submission can leave the outcome uncertain; do not automatically create a second job.
6. Poll and distinguish the result states
Section titled “6. Poll and distinguish the result states”Poll GET /generations/:jobId about every two seconds. Reuse the same visitor ID when available.
| Response state | UI action |
|---|---|
PROCESSING | Keep waiting. |
SUCCESS + PENDING | You may display displayUrl or imageUrl; continue polling and keep durable-result actions pending. |
SUCCESS + READY | Stop polling. Use permanentUrl or imageUrl. |
Legacy SUCCESS without storage status and with imageUrl | Treat as final. |
ERROR | Stop. Show the error; do not silently submit a replacement. |
| Unknown or malformed state | Treat as a protocol problem. Do not display success. |
A failed job is an HTTP 200 response with data.status: "ERROR". HTTP status alone is not enough.
Use a UI deadline and an AbortController or native cancellation mechanism. A suggested initial deadline is three minutes, chosen by your app; it is not an Antla guarantee. Save a received job ID so you can resume polling instead of generating again.
7. Add the selected item to cart
Section titled “7. Add the selected item to cart”Use Shopify Storefront Cart API directly. Antla’s API does not supply a cart endpoint. See Cart integration for mutation and attribution examples.
Keep job identity with the product
Section titled “Keep job identity with the product”Keep the selected product, variant, and job ID together so navigation cannot attach an old result to a new product. Stopping the poller does not cancel the server job.