Rate limits and error handling
An HTTP response and an asynchronous job result are different layers. Inspect both.
Error envelope
Section titled “Error envelope”{ "error": "try_on_limit_reached", "code": "TRY_ON_LIMIT_REACHED", "message": "Come back tomorrow for more!"}error is always present. code and message are present only for some failures. Branch on the status and code when code exists, and on the error string when it does not.
| Situation | Response |
|---|---|
| Product is not in the funnel | 403 { "error": "Product is not assigned to this funnel" } |
| Email required | 403 { "error": "email_required", "code": "EMAIL_REQUIRED" } |
| Customer try-on limit | 429 { "error": "try_on_limit_reached", "code": "TRY_ON_LIMIT_REACHED", "message": "Come back tomorrow for more!" } |
| Shared IP limit | 429 { "error": "Rate limit exceeded", "message": "Too many requests..." } and a Retry-After header |
Credits or queue failure on POST /generations | 500 { "error": "Could not create generation" } |
The try-on limit message is the merchant’s text. The sample above is the default. Show message to the shopper when it is present. Render it as text.
An inaccessible photo can fail after POST /generations has already returned a job ID. Generation does not reject a sold-out variant.
HTTP status reference
Section titled “HTTP status reference”| Status | Meaning | Client action |
|---|---|---|
| 200 | Successful HTTP operation; a polled job can still be ERROR | Inspect data. |
| 204 | OPTIONS preflight response | No JSON body. |
| 400 | Invalid input/JSON, missing product image or visitor registration problem | Correct the request; do not repeat unchanged. |
| 401 | Missing, invalid, refreshed or disconnected SDK key | Fix connection/environment configuration. |
| 403 | Product not in the Funnel, EMAIL_REQUIRED, or SDK_NOT_CONFIGURED on /context | Read code. Funnel mismatch has no code. API-only shops skip /context. |
| 404 | Product, variant, funnel, or job is missing or in another store | Check IDs against this SDK key’s store. |
| 405 | Unsupported HTTP method | Use the method documented for that path. |
| 429 | Shared IP limit, or TRY_ON_LIMIT_REACHED | Honor Retry-After only when the body is the IP limit. |
| 500 | Shopify, credits, queue, upload target, or a stored result that cannot be read | Keep a known job ID. Do not automatically resubmit a generation POST. |
Shared IP limit
Section titled “Shared IP limit”Every Headless route shares one counter, keyed by client IP, with the other Headless routes and with Antla’s theme app proxy. A poll increments the same count as a generation. The number in the table is the highest shared count that route will still accept. It is not a private budget.
| Operation | Rejected when the shared IP count in 60 seconds exceeds |
|---|---|
| Start generation | 120 |
| Poll generation | 240 |
| Create upload target | 120 |
| Resolve funnel | 120 |
| Get email status | 120 |
| Register customer | 60 |
| Get Storefront context | 120 |
A shopper network, or a backend that sends every shopper through one egress IP, shares that counter too. Pass each shopper’s uniqueUserId so visitor identity does not fall back to that shared IP.
An IP rate-limit response includes Retry-After in seconds. Browser JavaScript can read that header. A customer try-on limit is also HTTP 429 and does not include Retry-After.
Retry policy
Section titled “Retry policy”| Operation/outcome | Recommended behavior |
|---|---|
| GET poll: transient transport error or 5xx | Retry with a bounded backoff while preserving the same job ID and UI deadline. |
| GET poll: IP 429 with Retry-After | Wait the specified interval within your deadline; do not create a new job. |
POST generation: explicit EMAIL_REQUIRED | Collect email, then retry that rejected submission with the required input. |
| POST generation: explicit IP throttling | Wait, then retry the same attempt. Do not start a second job. |
| POST generation: network timeout, lost response or 5xx | Outcome may be unknown. Do not auto-resubmit. |
Job ERROR | Stop polling; show failure and let the user choose a new attempt. |
TRY_ON_LIMIT_REACHED | Show the merchant’s message. Do not run a retry loop. |
| Invalid/malformed successful response | Stop and report a protocol problem; do not infer a job ID or final URL. |
A POST can start work even if the client never receives a response. The API has no documented idempotency-key mechanism, cancellation endpoint or lookup by a client-generated request ID.
Timeouts and cancellation
Section titled “Timeouts and cancellation”Use an overall polling deadline and a per-request timeout. Cancel browser/native requests when the relevant screen is abandoned. This stops client work; it does not cancel Antla’s queued job.
If you already have a job ID, allow resuming its status check. A UI timeout is not proof that generation failed. Do not discard a temporary preview’s pending status just because your UI deadline elapsed.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Check |
|---|---|
| SDK does not appear | Script URL, key, product ID, Storefront product visibility, product image and funnel tag. |
| 401 on every request | Matching environment, active key and Headless SDK or API enabled. |
403 SDK_NOT_CONFIGURED on /context | Expected for API-only. Use your catalog, or connect Headless SDK. |
| Funnel 404 | Candidate tags are current and belong to this store. |
| Generation 403 | Exact product/funnel assignment; handle EMAIL_REQUIRED. |
| Generation 404 | Product/variant pair, funnel ownership and correct store key. |
| Upload PUT fails | Target expiry, matching MIME type, raw bytes, no SDK header, storage CORS where applicable. |
| API works but cart page is empty | Storefront and SDK share the same complete cart ID/session. |
| Theme event listener never fires | SDK does not implement the theme event contract. |
| Many 429s behind a backend | Shared IP counter, polling concurrency and visitor identity design. |
| Photo remains temporary | Continue polling; handle storage failure and timeout separately. |
For support, collect environment, endpoint, method, HTTP status, error code, approximate time and a job ID through an appropriate private channel. Redact keys, tokens, email, photo URLs and complete cart IDs.