{
  "openapi": "3.1.0",
  "info": {
    "title": "Antla Headless API",
    "version": "2026-09-28",
    "description": "Virtual try-on for one connected Shopify store. Headless API works without Storefront tokens. GET /context requires Headless SDK. Copy the production base URL from the merchant's Antla installation. There is no idempotency key, cancellation endpoint, or completion webhook.",
    "contact": {
      "name": "Antla",
      "email": "aaron@antla.io"
    }
  },
  "servers": [
    {
      "url": "https://app-antla.com/api/headless",
      "description": "Production"
    }
  ],
  "security": [
    {
      "AntlaSDKKey": []
    }
  ],
  "tags": [
    {
      "name": "Generations"
    },
    {
      "name": "Supporting"
    }
  ],
  "paths": {
    "/context": {
      "get": {
        "operationId": "context",
        "summary": "Get Storefront context",
        "description": "Load the Shopify Storefront API URL and public token. Requires Headless SDK (sdkEnabled and a stored public Storefront token). API-only installations receive 403 SDK_NOT_CONFIGURED. Shares the storefront IP rate limit.",
        "tags": [
          "Supporting"
        ],
        "responses": {
          "200": {
            "description": "Successful operation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/StorefrontContext"
                    }
                  }
                },
                "example": {
                  "data": {
                    "storefront": {
                      "apiUrl": "https://store.myshopify.com/api/2026-07/graphql.json",
                      "publicToken": "public-storefront-token"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "SDK key missing, invalid or disconnected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Headless SDK is not configured (SDK_NOT_CONFIGURED).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "SDK integration is not configured",
                  "code": "SDK_NOT_CONFIGURED"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Shared IP throttling or customer try-on limit. Inspect code and Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait; included for IP throttling, not guaranteed for customer-limit responses.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Server, Shopify, credit, storage or queue error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "x-configured-ip-threshold-per-60s": 120
      },
      "options": {
        "summary": "CORS preflight",
        "security": [],
        "responses": {
          "204": {
            "description": "No content. Allows Content-Type and X-Antla-SDK-Key cross-origin."
          }
        }
      }
    },
    "/funnel": {
      "get": {
        "operationId": "funnel",
        "summary": "Resolve a funnel",
        "description": "Validate candidate funnel IDs against the connected store and return the first valid match. Particularly useful when a product has multiple Antla funnel tags. Shares the storefront IP rate limit.",
        "tags": [
          "Supporting"
        ],
        "responses": {
          "200": {
            "description": "Successful operation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Funnel"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": 123,
                    "funnelType": "upper_body",
                    "generationModel": "ANTLA_PRO"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "SDK key missing, invalid or disconnected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found or outside the store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Shared IP throttling or customer try-on limit. Inspect code and Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait; included for IP throttling, not guaranteed for customer-limit responses.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Server, Shopify, credit, storage or queue error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "x-configured-ip-threshold-per-60s": 120,
        "parameters": [
          {
            "name": "funnelIds",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated positive integers in preferred order. Store-scoped, not a product-membership check.",
            "example": "123,456"
          }
        ]
      },
      "options": {
        "summary": "CORS preflight",
        "security": [],
        "responses": {
          "204": {
            "description": "No content. Allows Content-Type and X-Antla-SDK-Key cross-origin."
          }
        }
      }
    },
    "/uploads": {
      "post": {
        "operationId": "uploads",
        "summary": "Create an upload target",
        "description": "Prepare storage for a customer photo. The response contains a signed PUT target and the photo URL to use after that upload succeeds. Shares the storefront IP rate limit.",
        "tags": [
          "Supporting"
        ],
        "responses": {
          "200": {
            "description": "Successful operation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UploadTarget"
                    }
                  }
                },
                "example": {
                  "data": {
                    "uploadUrl": "https://storage.example.com/presigned-upload-url",
                    "imageUrl": "https://cdn.example.com/customer-photo.jpg"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "SDK key missing, invalid or disconnected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Shared IP throttling or customer try-on limit. Inspect code and Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait; included for IP throttling, not guaranteed for customer-limit responses.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Server, Shopify, credit, storage or queue error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "x-configured-ip-threshold-per-60s": 120,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UploadRequest"
              },
              "example": {
                "contentType": "image/jpeg"
              }
            }
          }
        }
      },
      "options": {
        "summary": "CORS preflight",
        "security": [],
        "responses": {
          "204": {
            "description": "No content. Allows Content-Type and X-Antla-SDK-Key cross-origin."
          }
        }
      }
    },
    "/requests-left": {
      "get": {
        "operationId": "requestsLeft",
        "summary": "Get email collection status",
        "description": "Determine whether a visitor must register an email before another try-on under the merchant’s email-collection settings. This may create an anonymous customer identity; it is not necessarily a side-effect-free counter read. Shares the storefront IP rate limit.",
        "tags": [
          "Supporting"
        ],
        "responses": {
          "200": {
            "description": "Successful operation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/VisitorStatus"
                    }
                  }
                },
                "example": {
                  "data": {
                    "requestsLeft": 2,
                    "totalRequests": 1,
                    "uniqueUserId": "visitor-id"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "SDK key missing, invalid or disconnected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Shared IP throttling or customer try-on limit. Inspect code and Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait; included for IP throttling, not guaranteed for customer-limit responses.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Server, Shopify, credit, storage or queue error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "x-configured-ip-threshold-per-60s": 120,
        "parameters": [
          {
            "name": "uniqueUserId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "description": "Saved visitor ID. Not job-access authentication."
          }
        ]
      },
      "options": {
        "summary": "CORS preflight",
        "security": [],
        "responses": {
          "204": {
            "description": "No content. Allows Content-Type and X-Antla-SDK-Key cross-origin."
          }
        }
      }
    },
    "/customer-register": {
      "post": {
        "operationId": "customerRegister",
        "summary": "Register a customer",
        "description": "Save a visitor’s email and optional name before continuing a generation. This registers an Antla visitor; it does not authenticate a Shopify customer. Shares the storefront IP rate limit.",
        "tags": [
          "Supporting"
        ],
        "responses": {
          "200": {
            "description": "Successful operation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/RegisteredVisitor"
                    }
                  }
                },
                "example": {
                  "data": {
                    "uniqueUserId": "visitor-id",
                    "requestsLeft": -1,
                    "totalRequests": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "SDK key missing, invalid or disconnected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Shared IP throttling or customer try-on limit. Inspect code and Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait; included for IP throttling, not guaranteed for customer-limit responses.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Server, Shopify, credit, storage or queue error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "x-configured-ip-threshold-per-60s": 60,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterRequest"
              },
              "example": {
                "email": "shopper@example.com",
                "name": "Alex",
                "uniqueUserId": "SAVED_VISITOR_ID"
              }
            }
          }
        }
      },
      "options": {
        "summary": "CORS preflight",
        "security": [],
        "responses": {
          "204": {
            "description": "No content. Allows Content-Type and X-Antla-SDK-Key cross-origin."
          }
        }
      }
    },
    "/generations": {
      "post": {
        "operationId": "createGeneration",
        "summary": "Start a generation",
        "description": "Queue a virtual try-on generation using the customer photo and Shopify product. Save the returned job ID and poll for completion. Shares the storefront IP rate limit.",
        "tags": [
          "Generations"
        ],
        "responses": {
          "200": {
            "description": "Successful operation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/GenerationStarted"
                    }
                  }
                },
                "example": {
                  "data": {
                    "jobId": "c45451c0-b02c-4c90-8c6e-0779edb01996",
                    "etaSeconds": 40,
                    "uniqueUserId": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "SDK key missing, invalid or disconnected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Product/funnel mismatch or email required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found or outside the store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Shared IP throttling or customer try-on limit. Inspect code and Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait; included for IP throttling, not guaranteed for customer-limit responses.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Server, Shopify, credit, storage or queue error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "x-configured-ip-threshold-per-60s": 120,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerationRequest"
              },
              "example": {
                "funnelId": 123,
                "productId": "gid://shopify/Product/123",
                "variantId": "gid://shopify/ProductVariant/456",
                "imageUrl": "https://YOUR_PUBLIC_IMAGE_HOST/customer-photo.jpg"
              }
            }
          }
        }
      },
      "options": {
        "summary": "CORS preflight",
        "security": [],
        "responses": {
          "204": {
            "description": "No content. Allows Content-Type and X-Antla-SDK-Key cross-origin."
          }
        }
      }
    },
    "/generations/{jobId}": {
      "get": {
        "operationId": "pollGeneration",
        "summary": "Poll a generation",
        "description": "Read a generation job belonging to the SDK key’s store. Poll about every two seconds until a final result or failure, with a deadline and cancellation in your UI. Shares the storefront IP rate limit. Polling counts against the same IP counter.",
        "tags": [
          "Generations"
        ],
        "responses": {
          "200": {
            "description": "Job state. HTTP 200 can contain a failed job.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/GenerationProcessing"
                        },
                        {
                          "$ref": "#/components/schemas/GenerationSuccess"
                        },
                        {
                          "$ref": "#/components/schemas/GenerationFailed"
                        }
                      ]
                    }
                  }
                },
                "example": {
                  "data": {
                    "status": "SUCCESS",
                    "storageStatus": "READY",
                    "imageUrl": "https://cdn.example.com/result.jpg",
                    "displayUrl": "https://provider.example.com/preview.jpg",
                    "permanentUrl": "https://cdn.example.com/result.jpg",
                    "imageId": 123,
                    "tryOnUuid": "try-on-uuid",
                    "requestsLeft": 2,
                    "totalRequests": 1,
                    "uniqueUserId": "visitor-id"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "SDK key missing, invalid or disconnected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found or outside the store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Shared IP throttling or customer try-on limit. Inspect code and Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait; included for IP throttling, not guaranteed for customer-limit responses.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Server, Shopify, credit, storage or queue error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "x-configured-ip-threshold-per-60s": 240,
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "uniqueUserId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "description": "Saved visitor ID. Not job-access authentication."
          }
        ]
      },
      "options": {
        "summary": "CORS preflight",
        "security": [],
        "responses": {
          "204": {
            "description": "No content. Allows Content-Type and X-Antla-SDK-Key cross-origin."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "AntlaSDKKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Antla-SDK-Key",
        "description": "Store-scoped public-use Antla SDK key. Not a Shopify token."
      }
    },
    "schemas": {
      "ApiError": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "StorefrontContext": {
        "type": "object",
        "required": [
          "storefront"
        ],
        "properties": {
          "storefront": {
            "type": "object",
            "required": [
              "apiUrl",
              "publicToken"
            ],
            "properties": {
              "apiUrl": {
                "type": "string",
                "format": "uri"
              },
              "publicToken": {
                "type": "string"
              }
            }
          }
        }
      },
      "Funnel": {
        "type": "object",
        "required": [
          "id",
          "funnelType",
          "generationModel"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 1
          },
          "funnelType": {
            "type": "string",
            "enum": [
              "upper_body",
              "lower_body",
              "dress",
              "accessory",
              "custom"
            ]
          },
          "generationModel": {
            "type": "string",
            "enum": [
              "ANTLA_PRO",
              "ANTLA_FAST",
              "ANTLA_LIGHT"
            ]
          }
        }
      },
      "UploadRequest": {
        "type": "object",
        "required": [
          "contentType"
        ],
        "properties": {
          "contentType": {
            "type": "string",
            "enum": [
              "image/jpeg",
              "image/png",
              "image/webp",
              "image/heic",
              "image/heif",
              "image/avif"
            ]
          },
          "customKey": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "pattern": "^[A-Za-z0-9_-]+$"
          }
        }
      },
      "UploadTarget": {
        "type": "object",
        "required": [
          "uploadUrl",
          "imageUrl"
        ],
        "properties": {
          "uploadUrl": {
            "type": "string",
            "format": "uri",
            "description": "PUT the raw image bytes here. Do not send the SDK key. Expires after 600 seconds."
          },
          "imageUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "VisitorStatus": {
        "type": "object",
        "required": [
          "requestsLeft",
          "totalRequests",
          "uniqueUserId"
        ],
        "properties": {
          "requestsLeft": {
            "type": "integer",
            "minimum": -1,
            "description": "Try-ons remaining before email is required. -1 means email collection is off or this visitor already has an email. Not merchant credits and not the rolling try-on cap."
          },
          "totalRequests": {
            "type": "integer",
            "minimum": 0,
            "description": "Try-ons already counted in the current email-collection period. Not the allowance."
          },
          "uniqueUserId": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "RegisterRequest": {
        "type": "object",
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "name": {
            "type": "string",
            "minLength": 1
          },
          "uniqueUserId": {
            "type": "string",
            "minLength": 1
          }
        }
      },
      "RegisteredVisitor": {
        "type": "object",
        "required": [
          "requestsLeft",
          "totalRequests",
          "uniqueUserId"
        ],
        "properties": {
          "requestsLeft": {
            "type": "integer",
            "minimum": -1,
            "description": "Try-ons remaining before email is required. -1 means email collection is off or this visitor already has an email. Not merchant credits and not the rolling try-on cap."
          },
          "totalRequests": {
            "type": "integer",
            "minimum": 0,
            "description": "Try-ons already counted in the current email-collection period. Not the allowance."
          },
          "uniqueUserId": {
            "type": "string",
            "minLength": 1
          }
        }
      },
      "GenerationRequest": {
        "type": "object",
        "required": [
          "funnelId",
          "productId",
          "variantId",
          "imageUrl"
        ],
        "properties": {
          "funnelId": {
            "type": "integer",
            "minimum": 1
          },
          "productId": {
            "oneOf": [
              {
                "type": "integer",
                "minimum": 1,
                "maximum": 9007199254740991
              },
              {
                "type": "string",
                "pattern": "^(?:gid://shopify/Product/)?(?!0$)[0-9]+$"
              }
            ],
            "description": "Prefer a numeric string or full Shopify GID to preserve precision."
          },
          "variantId": {
            "oneOf": [
              {
                "type": "integer",
                "minimum": 1,
                "maximum": 9007199254740991
              },
              {
                "type": "string",
                "pattern": "^(?:gid://shopify/ProductVariant/)?(?!0$)[0-9]+$"
              }
            ],
            "description": "Prefer a numeric string or full Shopify GID to preserve precision."
          },
          "imageUrl": {
            "type": "string",
            "format": "uri",
            "description": "A publicly retrievable customer photo URL. Syntactic validity does not guarantee generation success."
          },
          "uniqueUserId": {
            "type": "string",
            "minLength": 1
          },
          "customerEmail": {
            "type": "string",
            "format": "email"
          },
          "moreLooksConsent": {
            "type": "boolean"
          }
        }
      },
      "GenerationStarted": {
        "type": "object",
        "required": [
          "jobId",
          "etaSeconds",
          "uniqueUserId"
        ],
        "properties": {
          "jobId": {
            "type": "string",
            "format": "uuid"
          },
          "etaSeconds": {
            "type": "integer",
            "description": "Estimate; current implementation returns 40."
          },
          "uniqueUserId": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "GenerationProcessing": {
        "type": "object",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "const": "PROCESSING"
          }
        }
      },
      "GenerationFailed": {
        "type": "object",
        "required": [
          "status",
          "error"
        ],
        "properties": {
          "status": {
            "const": "ERROR"
          },
          "error": {
            "type": "string"
          }
        }
      },
      "GenerationSuccess": {
        "type": "object",
        "required": [
          "status",
          "imageUrl",
          "requestsLeft",
          "totalRequests",
          "uniqueUserId"
        ],
        "properties": {
          "status": {
            "const": "SUCCESS"
          },
          "storageStatus": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "PENDING",
              "READY",
              null
            ],
            "description": "PENDING: preview only, keep polling. READY: use permanentUrl or imageUrl. Missing or null with imageUrl: final. A storage failure is job status ERROR."
          },
          "imageUrl": {
            "type": "string",
            "format": "uri"
          },
          "displayUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "permanentUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "imageId": {
            "type": [
              "number",
              "null"
            ]
          },
          "tryOnUuid": {
            "type": [
              "string",
              "null"
            ]
          },
          "requestsLeft": {
            "type": "integer",
            "minimum": -1,
            "description": "Try-ons remaining before email is required. -1 means email collection is off or this visitor already has an email. Not merchant credits and not the rolling try-on cap."
          },
          "totalRequests": {
            "type": "integer",
            "minimum": 0,
            "description": "Try-ons already counted in the current email-collection period. Not the allowance."
          },
          "uniqueUserId": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      }
    }
  }
}
