<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: 煜华</title>
    <description>The latest articles on DEV Community by 煜华 (@_86f41ebeb2cab43917bd42).</description>
    <link>https://dev.to/_86f41ebeb2cab43917bd42</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4103624%2F5b8df0fa-1198-44ed-86c0-66f4f1d056fc.png</url>
      <title>DEV Community: 煜华</title>
      <link>https://dev.to/_86f41ebeb2cab43917bd42</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_86f41ebeb2cab43917bd42"/>
    <language>en</language>
    <item>
      <title>How I Plan a Veo 3.1 Product Video: One Buyer Question per Shot</title>
      <dc:creator>煜华</dc:creator>
      <pubDate>Fri, 04 Sep 2026 11:30:10 +0000</pubDate>
      <link>https://dev.to/_86f41ebeb2cab43917bd42/how-i-plan-a-veo-31-product-video-one-buyer-question-per-shot-490f</link>
      <guid>https://dev.to/_86f41ebeb2cab43917bd42/how-i-plan-a-veo-31-product-video-one-buyer-question-per-shot-490f</guid>
      <description>&lt;p&gt;When I plan an AI product video, I separate five jobs: product identity,&lt;br&gt;
demonstration, atmosphere, story and the final edit. I do not put them all&lt;br&gt;
into one prompt.&lt;/p&gt;

&lt;p&gt;Here is the shot-manifest template I recommend before calling the video API.&lt;br&gt;
It is a design proposal with illustrative examples, not a report of a paid&lt;br&gt;
generation experiment or a customer result.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: I work on XPLA. The XPLA fields below describe the contract I&lt;br&gt;
checked against the public documentation on September 4, 2026. This is not a price, speed, access or fidelity&lt;br&gt;
guarantee.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  One shot owns one buyer question
&lt;/h2&gt;

&lt;p&gt;“Make a premium product ad” is not a testable requirement.&lt;/p&gt;

&lt;p&gt;“Show where the touch control is and what changes after one tap” is.&lt;/p&gt;

&lt;p&gt;I split a sequence into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;evidence&lt;/strong&gt; — establish product identity;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;action&lt;/strong&gt; — show one mechanism;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;context&lt;/strong&gt; — show where the product is used.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The evidence shot runs first. If the product is wrong in a restrained shot, I&lt;br&gt;
do not spend more generations adding hands, moving props and dramatic camera&lt;br&gt;
motion.&lt;/p&gt;
&lt;h2&gt;
  
  
  The manifest
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ReviewState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;draft&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;approved&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submitted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accept&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;repair&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reject&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ShotManifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;shotId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;evidence&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;buyerQuestion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;referenceMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;frame&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ingredient&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;mustPreserve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;allowedMotion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;prohibitedImplications&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;acceptIf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;repairIf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;rejectIf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReviewState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ShotManifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;shotId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sku-014-action-01&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;buyerQuestion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;What does the closure look like in use?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;referenceMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;frame&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;mustPreserve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cap shape&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;label geometry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bottle color&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;allowedMotion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;slow hand approach&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;small camera push&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;prohibitedImplications&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;leakproof unless verified&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;acceptIf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;closure visible&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;label readable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;repairIf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crop hides contact point&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;rejectIf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cap, label or product count changes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;approved&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prompt is derived from this record. The QA decision returns to the same&lt;br&gt;
record.&lt;/p&gt;
&lt;h2&gt;
  
  
  I enforce a motion budget
&lt;/h2&gt;

&lt;p&gt;Each shot has three motion variables:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;product or hand action;&lt;/li&gt;
&lt;li&gt;camera movement;&lt;/li&gt;
&lt;li&gt;environmental movement.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Only one gets to be high variance.&lt;/p&gt;

&lt;p&gt;If a hand performs a precise mechanism, the camera and background stay quiet.&lt;br&gt;
If the camera performs an orbit, the product stays still. This is not a model&lt;br&gt;
rule; it is an application rule that makes failures explainable.&lt;/p&gt;
&lt;h2&gt;
  
  
  I validate the model contract separately
&lt;/h2&gt;

&lt;p&gt;The current XPLA public model is &lt;code&gt;veo-3.1-fast&lt;/code&gt; on &lt;code&gt;POST /v1/videos&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Its verified wrapper accepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fixed eight-second tasks;&lt;/li&gt;
&lt;li&gt;720p or 1080p;&lt;/li&gt;
&lt;li&gt;16:9 or 9:16;&lt;/li&gt;
&lt;li&gt;up to two ordered frame references;&lt;/li&gt;
&lt;li&gt;up to three ingredient references.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reference-video input is unsupported. Google's native Veo API has a different&lt;br&gt;
request shape, so I do not copy native provider fields into this wrapper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateShot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ShotManifest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buyerQuestion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Missing buyer question&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mustPreserve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Missing product truth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowedMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Motion budget too broad&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outcomes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;acceptIf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repairIf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rejectIf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outcomes&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Missing review rules&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A timeout is not a new creative
&lt;/h2&gt;

&lt;p&gt;The API task is asynchronous. I persist the intent before submitting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GenerationRecord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;shotId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;first_calibration&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;repair&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new_creative&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;requestHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;approved&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submitted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;transport_unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;nextAction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;poll&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reconcile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the client times out, the state becomes &lt;code&gt;transport_unknown&lt;/code&gt;. The application&lt;br&gt;
reconciles the original task before creating another generation.&lt;/p&gt;

&lt;p&gt;That distinction matters because a timeout does not prove the first task never&lt;br&gt;
started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Product QA runs before cinematic review
&lt;/h2&gt;

&lt;p&gt;My review order is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;identity;&lt;/li&gt;
&lt;li&gt;geometry and count;&lt;/li&gt;
&lt;li&gt;label and text;&lt;/li&gt;
&lt;li&gt;mechanism and contact points;&lt;/li&gt;
&lt;li&gt;physical plausibility;&lt;/li&gt;
&lt;li&gt;crop and channel fit;&lt;/li&gt;
&lt;li&gt;lighting and rhythm.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The outcome is explicit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;accept&lt;/strong&gt; — the shot answers its question and preserves product truth;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;repair&lt;/strong&gt; — the product is correct and a local layer can be fixed;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reject&lt;/strong&gt; — the product, mechanism, count, label or claim changed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A technically completed task is not publication approval.&lt;/p&gt;

&lt;h2&gt;
  
  
  The final ad is an edit
&lt;/h2&gt;

&lt;p&gt;An eight-second generation is a production unit, not necessarily the delivery&lt;br&gt;
length.&lt;/p&gt;

&lt;p&gt;I may trim an evidence clip to two seconds, an action clip to three and a&lt;br&gt;
context clip to three. Captions, approved voice, licensed sound and the CTA&lt;br&gt;
belong in the editing timeline.&lt;/p&gt;

&lt;p&gt;No paid generation was run for this article, and the diagram is a workflow&lt;br&gt;
explanation—not video evidence.&lt;/p&gt;

&lt;p&gt;The current wrapper details and request examples are in the&lt;br&gt;
&lt;a href="https://xplaai.com/en-us/api/veo-3-1-fast/" rel="noopener noreferrer"&gt;XPLA Veo 3.1 Fast guide&lt;/a&gt;.&lt;br&gt;
That page currently uses &lt;code&gt;noindex,follow&lt;/code&gt;; I am linking it for documentation,&lt;br&gt;
not claiming it is indexed or ranked.&lt;/p&gt;

&lt;p&gt;My stop rule is simple: if the first evidence shot changes a non-negotiable&lt;br&gt;
product fact twice, I return to the source material instead of growing the&lt;br&gt;
batch.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ecommerce</category>
      <category>typescript</category>
      <category>api</category>
    </item>
    <item>
      <title>Before I Call GPT Image 2, I Validate These 7 Things</title>
      <dc:creator>煜华</dc:creator>
      <pubDate>Wed, 02 Sep 2026 11:45:32 +0000</pubDate>
      <link>https://dev.to/_86f41ebeb2cab43917bd42/before-i-call-gpt-image-2-i-validate-these-7-things-3f6h</link>
      <guid>https://dev.to/_86f41ebeb2cab43917bd42/before-i-call-gpt-image-2-i-validate-these-7-things-3f6h</guid>
      <description>&lt;p&gt;I used to debug a bad AI product image by rewriting the prompt. Now I check&lt;br&gt;
the request contract first.&lt;/p&gt;

&lt;p&gt;A GPT Image 2 API request can be valid JSON and still be wrong for the selected&lt;br&gt;
model. A generic UI can leak a field from another provider. Two dimension&lt;br&gt;
controls can conflict. A timeout can turn one intended image into two&lt;br&gt;
generations. A technically successful output can still change the product.&lt;/p&gt;

&lt;p&gt;This is the seven-check preflight I put in front of the generation call.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: I work on XPLA. The workflow below is an engineering pattern,&lt;br&gt;
not a promise about price, access, speed or image quality.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  1. Select the contract before rendering the form
&lt;/h2&gt;

&lt;p&gt;I do not let one universal form submit every visible field to every image&lt;br&gt;
model. The application selects a model contract first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imageContracts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gpt-image-2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/v1/images/generations&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prompt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;images&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;size&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aspectRatio&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;quality&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;replyType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;imageSize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI should be generated from that record. A control the selected model does&lt;br&gt;
not support should not appear.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Reject unknown fields instead of dropping them
&lt;/h2&gt;

&lt;p&gt;Silently removing an unsupported field creates plausible but uncontrolled&lt;br&gt;
outputs. The user chooses a setting, the backend drops it, a default is used,&lt;br&gt;
and everyone blames the model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;rejectUnknownFields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Unsupported field for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Forbidden field for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An explicit local error is easier to fix than an attractive image produced&lt;br&gt;
from the wrong request.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Make one dimension decision
&lt;/h2&gt;

&lt;p&gt;An image form may expose a ratio, a pixel size, a provider-specific size field&lt;br&gt;
and an orientation inferred from the prompt. I allow one supported decision:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateDimensions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;imageSize&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;imageSize is not valid for this standard contract&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supplied&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aspectRatio&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
  &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;supplied&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Choose size or aspectRatio, not both&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I store the requested orientation beside the output so QA can verify it.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Treat a reference image as a rights record
&lt;/h2&gt;

&lt;p&gt;A reference is more than a URL. I store the product identity and the allowed&lt;br&gt;
transformation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"merchant_upload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rights_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"confirmed_for_internal_calibration"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"merchant-sku-104"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"variant"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"matte-black-500ml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"must_preserve"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"single bottle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"matte black body"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"silver cap"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"existing label geometry"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"allowed_changes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"surface"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"lighting direction"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A marketplace image being public does not automatically grant permission to&lt;br&gt;
download, transform or use it in advertising.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Separate retries from new generations
&lt;/h2&gt;

&lt;p&gt;This is the operational rule I care about most.&lt;/p&gt;

&lt;p&gt;If a client times out, it may not know whether the server failed before or&lt;br&gt;
after generation. Repeating the request can create another billable task.&lt;/p&gt;

&lt;p&gt;Before submission, I persist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"generation_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"img-job-20260902-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"intent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"new_calibration"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request_hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256-of-normalized-request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"submitted"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attempt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unknown"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"next_action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"check_before-repeating"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My state transition is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;draft
  -&amp;gt; approved
  -&amp;gt; submitted
  -&amp;gt; completed
  -&amp;gt; accepted | repair | rejected

submitted
  -&amp;gt; transport_unknown
  -&amp;gt; reconcile before another generation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A new creative candidate receives a new generation ID. An uncertain transport&lt;br&gt;
result enters reconciliation. I do not hide both actions behind one “Try&lt;br&gt;
again” button.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Generate one calibration image
&lt;/h2&gt;

&lt;p&gt;For the first run I use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one approved source;&lt;/li&gt;
&lt;li&gt;one product truth sheet;&lt;/li&gt;
&lt;li&gt;one background change;&lt;/li&gt;
&lt;li&gt;one orientation;&lt;/li&gt;
&lt;li&gt;one output;&lt;/li&gt;
&lt;li&gt;one named reviewer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The question is narrow: can the workflow preserve the product facts that&lt;br&gt;
matter? More outputs do not improve the evidence if no one reviews them.&lt;/p&gt;
&lt;h2&gt;
  
  
  7. Review product truth, not only aesthetics
&lt;/h2&gt;

&lt;p&gt;My QA table looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Expected&lt;/th&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product count&lt;/td&gt;
&lt;td&gt;one unit&lt;/td&gt;
&lt;td&gt;accept / repair / reject&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Body color&lt;/td&gt;
&lt;td&gt;matte black&lt;/td&gt;
&lt;td&gt;accept / repair / reject&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cap&lt;/td&gt;
&lt;td&gt;silver&lt;/td&gt;
&lt;td&gt;accept / repair / reject&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Label geometry&lt;/td&gt;
&lt;td&gt;unchanged&lt;/td&gt;
&lt;td&gt;accept / repair / reject&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invented claims&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;accept / repair / reject&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orientation&lt;/td&gt;
&lt;td&gt;requested ratio&lt;/td&gt;
&lt;td&gt;accept / repair / reject&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crop safety&lt;/td&gt;
&lt;td&gt;fully visible&lt;/td&gt;
&lt;td&gt;accept / repair / reject&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;“Looks good” is not a release decision. An attractive output can still invent&lt;br&gt;
a bundle, accessory, certification or quantity.&lt;/p&gt;
&lt;h2&gt;
  
  
  Complete preflight
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;preflightGPTImage2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rightsRecord&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;imageContracts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Unknown model contract: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prompt is required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Use n: 1 and create separate intentional requests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replyType&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replyType&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;replyType must be "json"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nf"&gt;rejectUnknownFields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nf"&gt;validateDimensions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;rightsRecord&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;rights_state&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;confirmed_for_internal_calibration&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Reference-image rights are not confirmed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is only the request boundary. A production implementation still needs&lt;br&gt;
media-type, file-size, URL, privacy, account, policy and storage checks.&lt;/p&gt;
&lt;h2&gt;
  
  
  Failure classes need different actions
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure class&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Default action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;empty prompt&lt;/td&gt;
&lt;td&gt;fix locally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contract&lt;/td&gt;
&lt;td&gt;unsupported field&lt;/td&gt;
&lt;td&gt;reject before submission&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access&lt;/td&gt;
&lt;td&gt;unauthorized key&lt;/td&gt;
&lt;td&gt;stop and fix access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transport&lt;/td&gt;
&lt;td&gt;lost response&lt;/td&gt;
&lt;td&gt;reconcile before repeating&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Provider&lt;/td&gt;
&lt;td&gt;transient failure&lt;/td&gt;
&lt;td&gt;bounded retry policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Safety&lt;/td&gt;
&lt;td&gt;risky request&lt;/td&gt;
&lt;td&gt;stop or revise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output QA&lt;/td&gt;
&lt;td&gt;altered product&lt;/td&gt;
&lt;td&gt;repair or reject&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I keep the final order simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract -&amp;gt; preflight -&amp;gt; approval -&amp;gt; one calibration
-&amp;gt; product QA -&amp;gt; intentional scale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current XPLA-specific request shape and model-name boundaries are&lt;br&gt;
documented in the &lt;a href="https://xplaai.com/en-us/api/gpt-image-2/" rel="noopener noreferrer"&gt;GPT Image 2 API guide&lt;/a&gt;.&lt;br&gt;
Recheck the live contract before production integration.&lt;/p&gt;

</description>
      <category>api</category>
      <category>ecommerce</category>
      <category>javascript</category>
      <category>ai</category>
    </item>
    <item>
      <title>One TikTok Shop Snapshot Is Not a Trend: The Research Board I Use</title>
      <dc:creator>煜华</dc:creator>
      <pubDate>Wed, 02 Sep 2026 04:48:34 +0000</pubDate>
      <link>https://dev.to/_86f41ebeb2cab43917bd42/one-tiktok-shop-snapshot-is-not-a-trend-the-research-board-i-use-5210</link>
      <guid>https://dev.to/_86f41ebeb2cab43917bd42/one-tiktok-shop-snapshot-is-not-a-trend-the-research-board-i-use-5210</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy9btcbbmfgkwaxubgak1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy9btcbbmfgkwaxubgak1.png" alt="Two comparable TikTok Shop product snapshots flow into a comparison step and human review, showing why one snapshot is not a trend." width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I get skeptical whenever a product-research sheet has a column called “7-day sales” but no collection window, source definition, or earlier observation.&lt;/p&gt;

&lt;p&gt;The number may still be useful. The label is the problem.&lt;/p&gt;

&lt;p&gt;A cumulative sales value answers, “What did this source report when I collected it?” It does not automatically answer, “What sold during the last seven days?” And one screenshot cannot tell me whether a product is accelerating.&lt;/p&gt;

&lt;p&gt;My rule is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The first collection creates a baseline. A trend requires comparable observations over time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sounds obvious, but it changes the whole workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I record in snapshot one
&lt;/h2&gt;

&lt;p&gt;Before ranking anything, I keep source facts separate from my interpretation.&lt;/p&gt;

&lt;p&gt;For each product I record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;collection time and time zone;&lt;/li&gt;
&lt;li&gt;source URL and query;&lt;/li&gt;
&lt;li&gt;market and currency;&lt;/li&gt;
&lt;li&gt;stable product identifier;&lt;/li&gt;
&lt;li&gt;seller, variant, bundle, color, or size when visible;&lt;/li&gt;
&lt;li&gt;price and promotion state;&lt;/li&gt;
&lt;li&gt;the exact name and definition of every metric;&lt;/li&gt;
&lt;li&gt;missing fields as missing, not as zero.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also write the research question before collecting products. “Find winners” is not a useful question. “Which home-organization products deserve a second snapshot?” is.&lt;/p&gt;

&lt;p&gt;The narrower question gives me a defensible stopping point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The comparability gate
&lt;/h2&gt;

&lt;p&gt;Before calculating a change, I compare the two records.&lt;/p&gt;

&lt;p&gt;They must refer to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the same product identifier;&lt;/li&gt;
&lt;li&gt;the same variant or bundle;&lt;/li&gt;
&lt;li&gt;the same market and currency;&lt;/li&gt;
&lt;li&gt;the same metric definition;&lt;/li&gt;
&lt;li&gt;the same collection method.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If any of those changed, I do not calculate a trend line.&lt;/p&gt;

&lt;p&gt;A similar title or image is not enough. A two-pack and a single item can share a listing concept while representing different commercial units. A promotion can change the observed price without changing the underlying product. A seller can replace a variant while keeping familiar creative.&lt;/p&gt;

&lt;p&gt;The product identity has to survive the comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I calculate
&lt;/h2&gt;

&lt;p&gt;If the observations are comparable, the arithmetic is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;snapshot_difference = later_cumulative_value - earlier_cumulative_value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But I label the output &lt;strong&gt;snapshot difference&lt;/strong&gt;, not “demand,” “growth,” or “future sales.”&lt;/p&gt;

&lt;p&gt;Why the restraint?&lt;/p&gt;

&lt;p&gt;Because the difference may still be affected by elapsed time, reporting delay, promotion changes, listing edits, inventory, or source behavior. Two observations improve the evidence. They do not explain every cause.&lt;/p&gt;

&lt;p&gt;For any rate, I preserve the denominator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;observed_rate = snapshot_difference / elapsed_time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I never compare a two-day interval with a fourteen-day interval as if the raw differences were equivalent.&lt;/p&gt;

&lt;h2&gt;
  
  
  A minimal comparison record
&lt;/h2&gt;

&lt;p&gt;This is the smallest structure I find useful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stable-source-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"market"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metric_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sales_cumulative"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"snapshot_1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"collected_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ISO-8601 timestamp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"source value"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"snapshot_2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"collected_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ISO-8601 timestamp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"source value"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"comparison_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"comparable | hold | reject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"review_note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"what changed, what is unknown, and what happens next"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important field is not the score. It is &lt;code&gt;comparison_state&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;An automation should be allowed to say &lt;strong&gt;hold&lt;/strong&gt; when identity, policy, sourcing, or field definitions are unclear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data is not the final gate
&lt;/h2&gt;

&lt;p&gt;I do not send every product with a positive difference into creative production.&lt;/p&gt;

&lt;p&gt;First I check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;category and qualification requirements;&lt;/li&gt;
&lt;li&gt;product and variant identity;&lt;/li&gt;
&lt;li&gt;sourcing and fulfillment unknowns;&lt;/li&gt;
&lt;li&gt;claim, brand, image, and music rights;&lt;/li&gt;
&lt;li&gt;whether the product can be demonstrated truthfully;&lt;/li&gt;
&lt;li&gt;whether the team has a bounded test and stop condition.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A metric can help prioritize investigation. It cannot make the product compliant, profitable, demonstrable, or suitable for an audience.&lt;/p&gt;

&lt;p&gt;This is where human review belongs: after the comparison, before the content factory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning research into a creative brief
&lt;/h2&gt;

&lt;p&gt;Only an approved candidate becomes a creative brief.&lt;/p&gt;

&lt;p&gt;I translate the evidence into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one customer problem;&lt;/li&gt;
&lt;li&gt;three supportable product facts;&lt;/li&gt;
&lt;li&gt;one required demonstration;&lt;/li&gt;
&lt;li&gt;common objections;&lt;/li&gt;
&lt;li&gt;claims the content must avoid;&lt;/li&gt;
&lt;li&gt;three distinct creative hypotheses;&lt;/li&gt;
&lt;li&gt;an owner, review point, and stop condition.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice what is missing: “make it viral.”&lt;/p&gt;

&lt;p&gt;The creative team needs a testable angle, not a promise.&lt;/p&gt;

&lt;p&gt;For example, a storage product might support three honest hypotheses:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;show the space problem before the product;&lt;/li&gt;
&lt;li&gt;demonstrate the exact capacity with a fixed set of objects;&lt;/li&gt;
&lt;li&gt;compare setup friction, without inventing a competitor.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those are creative directions. They are not predictions of sales.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where an API fits
&lt;/h2&gt;

&lt;p&gt;An API is useful when I need repeatable queries, stable identifiers, stored timestamps, and later comparisons. It does not remove the research contract.&lt;/p&gt;

&lt;p&gt;I still need to define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what a field means;&lt;/li&gt;
&lt;li&gt;which market it describes;&lt;/li&gt;
&lt;li&gt;whether the value is cumulative;&lt;/li&gt;
&lt;li&gt;how errors and missing data are stored;&lt;/li&gt;
&lt;li&gt;when the workflow stops for human review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I work on XPLA, where we document a TikTok Shop data path for product search and supported product details. The public guide explicitly keeps cumulative values separate from time-window claims:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://xplaai.com/en-us/api/tiktok-shop-data/" rel="noopener noreferrer"&gt;https://xplaai.com/en-us/api/tiktok-shop-data/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That page is an implementation starting point, not a “winning product” guarantee.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checklist I keep beside the query
&lt;/h2&gt;

&lt;p&gt;Before calling a product a trend, I ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do I have at least two comparable observations?&lt;/li&gt;
&lt;li&gt;Is the product identity unchanged?&lt;/li&gt;
&lt;li&gt;Is the metric definition explicit?&lt;/li&gt;
&lt;li&gt;Is the elapsed time recorded?&lt;/li&gt;
&lt;li&gt;Are missing fields still missing?&lt;/li&gt;
&lt;li&gt;Have policy and rights been reviewed?&lt;/li&gt;
&lt;li&gt;Is the conclusion labeled as an observation rather than a forecast?&lt;/li&gt;
&lt;li&gt;Is a human responsible for the next decision?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer to one of the first four questions is no, I do not call it a trend.&lt;/p&gt;

&lt;p&gt;That discipline makes the research slower for a few minutes—and far more useful for every decision that follows.&lt;/p&gt;

</description>
      <category>tiktok</category>
      <category>ecommerce</category>
      <category>data</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>AI Product Photography: The Product Truth Sheet I Use Before Prompting</title>
      <dc:creator>煜华</dc:creator>
      <pubDate>Tue, 01 Sep 2026 10:47:36 +0000</pubDate>
      <link>https://dev.to/_86f41ebeb2cab43917bd42/ai-product-photography-the-product-truth-sheet-i-use-before-prompting-hg</link>
      <guid>https://dev.to/_86f41ebeb2cab43917bd42/ai-product-photography-the-product-truth-sheet-i-use-before-prompting-hg</guid>
      <description>&lt;p&gt;When I review an &lt;strong&gt;AI product photography&lt;/strong&gt; workflow, I no longer start with a prompt like “make this look professional.”&lt;/p&gt;

&lt;p&gt;That sentence sounds useful. It is not. It says nothing about which product details must survive, which parts may change, where the image will appear, or what would make the result unsafe to publish.&lt;/p&gt;

&lt;p&gt;I start with a product truth sheet and one small calibration instead.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpbs.twimg.com%2Fmedia%2FHRIAgMoboAAbvsv%3Fformat%3Djpg%26name%3Dmedium" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpbs.twimg.com%2Fmedia%2FHRIAgMoboAAbvsv%3Fformat%3Djpg%26name%3Dmedium" alt="Seven-step AI product photography workflow: choose the job, lock product truth, check rights, decide what to preserve, create one or two calibrations, run QA, then repair or approve a small set." width="1200" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Direct answer
&lt;/h2&gt;

&lt;p&gt;Do not ask an image model for a full campaign on the first run.&lt;/p&gt;

&lt;p&gt;First, choose one image job. Record the visible and verified product facts that must not change. List what the source image does not show and what the output must never imply. Confirm rights. Then make one or two calibration images.&lt;/p&gt;

&lt;p&gt;I only expand the set after geometry, color, logo, label text, scale, scene, and crop pass review.&lt;/p&gt;

&lt;p&gt;This is less exciting than writing a giant cinematic prompt. It is also much easier to audit.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. I define the image’s job before choosing a tool
&lt;/h2&gt;

&lt;p&gt;“Product photo” can mean several different things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a clean marketplace main image;&lt;/li&gt;
&lt;li&gt;a background replacement;&lt;/li&gt;
&lt;li&gt;a product-page lifestyle image;&lt;/li&gt;
&lt;li&gt;an organic social post;&lt;/li&gt;
&lt;li&gt;a paid-ad draft;&lt;/li&gt;
&lt;li&gt;an on-model apparel image;&lt;/li&gt;
&lt;li&gt;an early concept that will never be used as a listing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those jobs should not share one acceptance test.&lt;/p&gt;

&lt;p&gt;A marketplace image may need the product to remain almost untouched. A lifestyle image can add a room, surface, prop, and atmosphere, but those additions can imply scale or usage. An apparel image must keep the garment’s construction while also handling the model, pose, anatomy, and body contact.&lt;/p&gt;

&lt;p&gt;Before I open a generator, I write one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This image will be used for &lt;strong&gt;[destination]&lt;/strong&gt; to show &lt;strong&gt;[one product fact or use context]&lt;/strong&gt;. It will not be treated as &lt;strong&gt;[listing evidence, performance proof, or another excluded use]&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If I cannot finish that sentence, I am not ready to generate.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. I build a one-page product truth sheet
&lt;/h2&gt;

&lt;p&gt;The product truth sheet is the part I wish more prompt tutorials included.&lt;/p&gt;

&lt;p&gt;It has four sections.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visible facts
&lt;/h3&gt;

&lt;p&gt;These are details a reviewer can confirm from the approved source images:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;silhouette and proportions;&lt;/li&gt;
&lt;li&gt;visible color and finish;&lt;/li&gt;
&lt;li&gt;logo position;&lt;/li&gt;
&lt;li&gt;label layout;&lt;/li&gt;
&lt;li&gt;seams, pockets, buttons, closures, ports, handles, and included parts;&lt;/li&gt;
&lt;li&gt;the visible front, side, and back structure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Verified facts that are not visible
&lt;/h3&gt;

&lt;p&gt;These come from a current product specification or another approved source:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dimensions;&lt;/li&gt;
&lt;li&gt;material;&lt;/li&gt;
&lt;li&gt;capacity;&lt;/li&gt;
&lt;li&gt;compatibility;&lt;/li&gt;
&lt;li&gt;included accessories;&lt;/li&gt;
&lt;li&gt;official variant and color names.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I do not add these because they “seem likely.” A plausible detail is still an invented detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unknown details
&lt;/h3&gt;

&lt;p&gt;This is where I record what the model is not allowed to guess:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an unseen back label;&lt;/li&gt;
&lt;li&gt;a hidden closure;&lt;/li&gt;
&lt;li&gt;the inside of the package;&lt;/li&gt;
&lt;li&gt;the underside of a device;&lt;/li&gt;
&lt;li&gt;the way a fabric behaves when worn;&lt;/li&gt;
&lt;li&gt;a product variant with no source photo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The word &lt;em&gt;unknown&lt;/em&gt; does real work. It gives the reviewer permission to reject an attractive image.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prohibited implications
&lt;/h3&gt;

&lt;p&gt;I list anything the image must not quietly claim:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an unverified medical, safety, environmental, or performance benefit;&lt;/li&gt;
&lt;li&gt;a certification that is not documented;&lt;/li&gt;
&lt;li&gt;a before-and-after result that was never tested;&lt;/li&gt;
&lt;li&gt;a celebrity or real person without rights;&lt;/li&gt;
&lt;li&gt;competitor branding;&lt;/li&gt;
&lt;li&gt;unsafe product use.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without this sheet, “consistent” often means only “the outputs look similar to each other.” That is not the same as being faithful to the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. I decide what must be preserved and what may be generated
&lt;/h2&gt;

&lt;p&gt;The most useful question is not always “Which image model should I use?”&lt;/p&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which pixels must remain evidence, and which pixels are allowed to become creative material?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the label, shape, or finish is important, I prefer a workflow that preserves the original product layer and changes the background, surface, shadow, or composition around it.&lt;/p&gt;

&lt;p&gt;Regenerating the whole frame gives the model more freedom. That may be fine for a concept image. It is risky when the image will help a buyer decide what arrives in the box.&lt;/p&gt;

&lt;p&gt;I use three rough modes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Preserve&lt;/strong&gt; — keep the product pixels and edit the surrounding scene.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Constrain&lt;/strong&gt; — allow limited product edits but require a strict detail review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concept&lt;/strong&gt; — allow invention, label the result clearly, and keep it away from listing evidence.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The mode belongs in the brief. It should not be guessed after the output looks good.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. I check rights before I describe the scene
&lt;/h2&gt;

&lt;p&gt;Every input has a rights question:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who owns the product photo?&lt;/li&gt;
&lt;li&gt;Can the brand mark be used in this channel?&lt;/li&gt;
&lt;li&gt;Is the model authorized for this use?&lt;/li&gt;
&lt;li&gt;Is the location or reference image licensed?&lt;/li&gt;
&lt;li&gt;Does the intended output use fit the relevant terms?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If I create an original adult model for apparel, I do not ask the system to imitate a real person. “Make her look like this celebrity” is not a harmless style instruction.&lt;/p&gt;

&lt;p&gt;A beautiful output does not repair a missing permission record.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. I separate preservation rules from scene direction
&lt;/h2&gt;

&lt;p&gt;I write the prompt in two blocks.&lt;/p&gt;

&lt;p&gt;The first block protects product truth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PRESERVE
- exact silhouette and proportions
- current navy color and matte finish
- logo position and readable label layout
- included cap and visible connector

DO NOT INVENT
- back label
- extra accessories
- certification marks
- liquid, smoke, sparks, or performance effects
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second block describes the scene:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SCENE
- clean editorial product photography
- product centered on a light stone shelf
- one folded neutral towel behind the product
- soft morning bathroom background
- realistic contact shadow
- no people, hands, text overlay, or additional products
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Google’s current Product Studio guidance for its own background workflow asks merchants to describe the product, placement, surroundings, and background. I use those fields because they force the scene to become specific. I do not treat them as a universal contract for every image tool.&lt;/p&gt;

&lt;p&gt;Vague praise—“premium,” “viral,” “luxury,” “amazing”—does not tell a reviewer what should pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. I create one or two calibration images
&lt;/h2&gt;

&lt;p&gt;My default is a small calibration, not a ten-image batch.&lt;/p&gt;

&lt;p&gt;For each calibration, I record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;source-image revision;&lt;/li&gt;
&lt;li&gt;prompt revision;&lt;/li&gt;
&lt;li&gt;intended channel;&lt;/li&gt;
&lt;li&gt;requested output count;&lt;/li&gt;
&lt;li&gt;task or request count;&lt;/li&gt;
&lt;li&gt;approved cost ceiling;&lt;/li&gt;
&lt;li&gt;returned output;&lt;/li&gt;
&lt;li&gt;pass, repair, or reject decision.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I compare the result with the product truth sheet.&lt;/p&gt;

&lt;p&gt;The purpose is not to find one lucky image. It is to decide whether the direction is controlled enough to continue.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. I run product QA and channel QA separately
&lt;/h2&gt;

&lt;p&gt;I review the product first:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;What I look for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Geometry&lt;/td&gt;
&lt;td&gt;shape, proportions, openings, handles, closures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Color&lt;/td&gt;
&lt;td&gt;variant, tone, finish, unexpected gradients&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;logo, label, spelling, layout, invented marks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scale&lt;/td&gt;
&lt;td&gt;product size relative to hands, furniture, or props&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contact&lt;/td&gt;
&lt;td&gt;believable shadow, grip, surface contact, garment fit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Completeness&lt;/td&gt;
&lt;td&gt;included parts present, no invented accessories&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then I review the destination:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;What I look for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claim safety&lt;/td&gt;
&lt;td&gt;scene does not imply an unverified result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crop&lt;/td&gt;
&lt;td&gt;important product details survive the placement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platform fit&lt;/td&gt;
&lt;td&gt;current format and content rules are checked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rights&lt;/td&gt;
&lt;td&gt;intended use matches the recorded permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Traceability&lt;/td&gt;
&lt;td&gt;source, prompt, output, reviewer, and decision are stored&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An API success and a publication approval are different events. I keep them separate.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. I repair the failed layer instead of rerolling everything
&lt;/h2&gt;

&lt;p&gt;If the product is right but the shadow floats, I repair the shadow.&lt;/p&gt;

&lt;p&gt;If the scene works but the label changes, I return to the preserved product layer.&lt;/p&gt;

&lt;p&gt;If the model invents a hidden detail, I add another source view or change the camera angle so the output does not need that detail.&lt;/p&gt;

&lt;p&gt;Blind regeneration hides the cause of failure. It also makes it harder to tell whether a workflow is improving or just producing more attempts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The apparel branch I verified in XPLA
&lt;/h2&gt;

&lt;p&gt;I work on XPLA, so I checked its current apparel workflow before mentioning it here.&lt;/p&gt;

&lt;p&gt;The live Fashion Street Shoot Skill is intentionally narrower than this general tutorial. It accepts one garment product photo and either an authorized adult model or an original adult model. The workflow starts with two calibration images. After approval, it expands to a 5–10 image, 3:4 street-photo set and creates a QA review page.&lt;/p&gt;

&lt;p&gt;That is an apparel workflow. I would not use its existence to claim that XPLA supports every product category or that every output will preserve every detail. Current account access, model usage, price, and output quality still need to be checked at the time of use.&lt;/p&gt;

&lt;p&gt;The useful idea is the gate: lock the person, garment, and visual direction with two images before paying to expand the set.&lt;/p&gt;

&lt;h2&gt;
  
  
  A compact checklist
&lt;/h2&gt;

&lt;p&gt;Before generation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] one destination and one image job;&lt;/li&gt;
&lt;li&gt;[ ] rights-cleared source;&lt;/li&gt;
&lt;li&gt;[ ] visible and verified product facts;&lt;/li&gt;
&lt;li&gt;[ ] unknown and prohibited details;&lt;/li&gt;
&lt;li&gt;[ ] preserve, constrain, or concept mode;&lt;/li&gt;
&lt;li&gt;[ ] one prompt revision;&lt;/li&gt;
&lt;li&gt;[ ] task and cost ceiling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before expansion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] geometry passed;&lt;/li&gt;
&lt;li&gt;[ ] color passed;&lt;/li&gt;
&lt;li&gt;[ ] logo and text passed or have an approved repair;&lt;/li&gt;
&lt;li&gt;[ ] scale and contact are plausible;&lt;/li&gt;
&lt;li&gt;[ ] scene makes no unsupported claim;&lt;/li&gt;
&lt;li&gt;[ ] channel crop and rules checked;&lt;/li&gt;
&lt;li&gt;[ ] source, output, and decision archived.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;One source angle cannot define hidden geometry.&lt;/li&gt;
&lt;li&gt;Generative tools can alter logos, labels, patterns, seams, reflections, hands, packaging, and scale.&lt;/li&gt;
&lt;li&gt;A realistic scene can imply a false claim without adding any text.&lt;/li&gt;
&lt;li&gt;Marketplace and advertising rules change.&lt;/li&gt;
&lt;li&gt;A method that works for a bottle may fail for apparel, jewelry, reflective metal, furniture, or food.&lt;/li&gt;
&lt;li&gt;One approved image does not prove a tool is universally “best.”&lt;/li&gt;
&lt;li&gt;More images do not prove more clicks, orders, or revenue.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a product truth sheet?
&lt;/h3&gt;

&lt;p&gt;It is a short review document that separates visible facts, verified facts, unknown details, and prohibited implications. It becomes the acceptance standard for prompts and outputs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I replace the background or regenerate the whole product?
&lt;/h3&gt;

&lt;p&gt;If exact product details matter, preserve the product layer when possible and change the surrounding scene. Full-frame regeneration belongs in a stricter QA workflow or a clearly labeled concept stage.&lt;/p&gt;

&lt;h3&gt;
  
  
  How many calibration images should I create?
&lt;/h3&gt;

&lt;p&gt;I usually start with one or two. The goal is to approve a direction before creating a larger set, not to search through a large batch for one lucky result.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I keep a logo or label accurate?
&lt;/h3&gt;

&lt;p&gt;Use the clearest approved source, preserve original pixels when possible, add detail views, and compare every output with the truth sheet. Reject or repair altered text rather than explaining it away.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can one source photo support a full lifestyle set?
&lt;/h3&gt;

&lt;p&gt;Sometimes, but only for what the source actually shows. If a new angle reveals hidden geometry, provide another view or avoid that angle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does a technically successful output count as approved?
&lt;/h3&gt;

&lt;p&gt;No. Technical completion only proves the request finished. Product truth, rights, claims, crop, and channel fit still need review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and disclosure
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Google Merchant Center: &lt;a href="https://support.google.com/merchants/answer/13708167?hl=en" rel="noopener noreferrer"&gt;About Product Studio&lt;/a&gt;, rechecked September 1, 2026. I use its scene fields as a bounded example, not a universal tool contract.&lt;/li&gt;
&lt;li&gt;XPLA: &lt;a href="https://xplaai.com/skills/xpla-fashion-street-shoot" rel="noopener noreferrer"&gt;Fashion Street Shoot Skill&lt;/a&gt;, rechecked September 1, 2026.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Disclosure: I prepared this article for XPLA. I verified the live page and current repository wording, but I did not invent a paid run, customer result, sales lift, traffic result, or tool ranking.&lt;/p&gt;

&lt;p&gt;If your product is apparel, the live XPLA page shows the narrower one-garment, two-calibration, approve-then-expand workflow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://xplaai.com/skills/xpla-fashion-street-shoot" rel="noopener noreferrer"&gt;https://xplaai.com/skills/xpla-fashion-street-shoot&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ecommerce</category>
      <category>tutorial</category>
      <category>marketing</category>
    </item>
    <item>
      <title>Unified AI API Tutorial: Keep Model Contracts Explicit</title>
      <dc:creator>煜华</dc:creator>
      <pubDate>Tue, 01 Sep 2026 06:41:49 +0000</pubDate>
      <link>https://dev.to/_86f41ebeb2cab43917bd42/unified-ai-api-tutorial-keep-model-contracts-explicit-462</link>
      <guid>https://dev.to/_86f41ebeb2cab43917bd42/unified-ai-api-tutorial-keep-model-contracts-explicit-462</guid>
      <description>&lt;h2&gt;
  
  
  Why I wrote this
&lt;/h2&gt;

&lt;p&gt;When I review a unified AI API, I do not start by counting model names. I start by checking which contracts are explicit, which jobs are asynchronous, and where the application must keep its own validation and review gates. This is the contract-first checklist I use to separate a useful access layer from a misleading “one schema for everything” abstraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Direct answer
&lt;/h2&gt;

&lt;p&gt;A useful &lt;strong&gt;unified AI API&lt;/strong&gt; gives your application one access layer without&lt;br&gt;
pretending that every model behaves the same.&lt;/p&gt;

&lt;p&gt;Centralizing the account, base URL, authentication pattern, and route discovery&lt;br&gt;
can reduce integration overhead. But image, video, speech, chat, and commerce&lt;br&gt;
tasks still have different parameters, response patterns, and review&lt;br&gt;
requirements. A reliable implementation keeps those contracts explicit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fxplaai.com%2Fmedia%2Funified-ai-api-request-lifecycle-en-us.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fxplaai.com%2Fmedia%2Funified-ai-api-request-lifecycle-en-us.svg" alt="Contract-first unified AI API flow from one Bearer token to separate chat, image, speech, video, and commerce route adapters." width="1200" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This guide shows a contract-first workflow using XPLA's documented route&lt;br&gt;
families as the example. The method also applies when you are designing your&lt;br&gt;
own multi-model gateway.&lt;/p&gt;
&lt;h2&gt;
  
  
  What should a unified AI API actually unify?
&lt;/h2&gt;

&lt;p&gt;The word &lt;em&gt;unified&lt;/em&gt; is useful when it describes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one account and access layer;&lt;/li&gt;
&lt;li&gt;one base URL;&lt;/li&gt;
&lt;li&gt;one Bearer-token pattern;&lt;/li&gt;
&lt;li&gt;one place to discover current models;&lt;/li&gt;
&lt;li&gt;explicit routes for different task families;&lt;/li&gt;
&lt;li&gt;a consistent place to check current documentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It becomes misleading when it implies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one universal request body;&lt;/li&gt;
&lt;li&gt;identical model parameters;&lt;/li&gt;
&lt;li&gt;identical synchronous or asynchronous behavior;&lt;/li&gt;
&lt;li&gt;a fixed catalog available to every account;&lt;/li&gt;
&lt;li&gt;guaranteed failover, lower cost, latency, throughput, or uptime.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The safest architecture unifies access while preserving model contracts.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Define the application job before choosing a model
&lt;/h2&gt;

&lt;p&gt;Start with the result your user needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is the input?&lt;/li&gt;
&lt;li&gt;What output must be delivered?&lt;/li&gt;
&lt;li&gt;Is a response needed immediately?&lt;/li&gt;
&lt;li&gt;Can the task run asynchronously?&lt;/li&gt;
&lt;li&gt;Does the task contain private media?&lt;/li&gt;
&lt;li&gt;What rights and retention rules apply?&lt;/li&gt;
&lt;li&gt;What must a human review before the result is used?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This decision should select a route family before it selects a model name.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Application job&lt;/th&gt;
&lt;th&gt;XPLA route&lt;/th&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Discover models available to a token&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /v1/models&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;synchronous JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generate or edit an image&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /v1/images/generations&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;synchronous for supported contracts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Create a video task&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /v1/videos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;asynchronous&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Check a video task&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /v1/videos/{task_id}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;poll by task ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retrieve completed video content&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /v1/videos/{task_id}/content&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;authenticated content read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run a chat completion&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /v1/chat/completions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;completion or streaming contract&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generate speech&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /v1/audio/speech&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text-to-speech contract&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search TikTok Shop data&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /v1/tiktok/shop/search&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;commerce-data query&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Get one TikTok product&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /v1/tiktok/product&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;product lookup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parse an authorized public video&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /v1/video/parse&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;public-video analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The table is a route-selection aid, not a guarantee that every model is enabled&lt;br&gt;
for every account. Check the current documentation before implementation.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Discover models instead of copying an old model name
&lt;/h2&gt;

&lt;p&gt;Keep the real API key outside source code, screenshots, browser JavaScript, and&lt;br&gt;
shared prompts. In a server-side shell, assign it to an environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;XPLA_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"replace_with_your_key"&lt;/span&gt;

curl &lt;span class="nt"&gt;--request&lt;/span&gt; GET &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://xplaai.com/v1/models &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$XPLA_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Treat the response as a current capability input, not a permanent catalog.&lt;br&gt;
Before you expose a model in your product, record:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;its exact public name;&lt;/li&gt;
&lt;li&gt;the endpoint family it belongs to;&lt;/li&gt;
&lt;li&gt;the accepted fields;&lt;/li&gt;
&lt;li&gt;whether the result is synchronous or asynchronous;&lt;/li&gt;
&lt;li&gt;the last contract-review date;&lt;/li&gt;
&lt;li&gt;the output checks required by your application.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not silently replace one model with another when their parameters differ.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Build a model-contract registry
&lt;/h2&gt;

&lt;p&gt;A route adapter should not accept every possible field and forward it blindly.&lt;br&gt;
Instead, keep an explicit registry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ModelContract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v1/images/generations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v1/videos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;allowedFields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;synchronous&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;asynchronous&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;reviewedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contracts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ModelContract&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-image-2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v1/images/generations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;allowedFields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;model&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prompt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;size&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;images&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quality&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;synchronous&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;reviewedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-08-31&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;veo-3.1-fast&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v1/videos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;allowedFields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;model&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prompt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;seconds&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resolution&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ratio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;images&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;metadata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;asynchronous&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;reviewedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-08-31&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an illustrative registry based on a dated contract review. Generate&lt;br&gt;
production values from the documentation and tests your team has actually&lt;br&gt;
approved.&lt;/p&gt;

&lt;p&gt;The review date should trigger revalidation. It is not decorative metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Separate synchronous results from asynchronous tasks
&lt;/h2&gt;

&lt;p&gt;Supported image-generation contracts can return a result in the initial&lt;br&gt;
response. Video creation requires a task lifecycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;send &lt;code&gt;POST /v1/videos&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;persist the returned task ID;&lt;/li&gt;
&lt;li&gt;poll &lt;code&gt;GET /v1/videos/{task_id}&lt;/code&gt; with bounded intervals;&lt;/li&gt;
&lt;li&gt;stop polling on a documented terminal state;&lt;/li&gt;
&lt;li&gt;retrieve ready content through
&lt;code&gt;GET /v1/videos/{task_id}/content&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;send the output to rights and quality review.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not keep a task ID only in browser memory. A refresh, worker restart, or&lt;br&gt;
temporary network failure should not make the application lose the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Normalize errors without erasing the original cause
&lt;/h2&gt;

&lt;p&gt;Your product can present a small internal error taxonomy, but preserve the&lt;br&gt;
original route, public model name, status, and task ID.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Application action&lt;/th&gt;
&lt;th&gt;Avoid&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;401&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;stop and request a valid server-side token&lt;/td&gt;
&lt;td&gt;sending the key to client analytics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;400&lt;/code&gt; or validation error&lt;/td&gt;
&lt;td&gt;show the rejected field and selected contract&lt;/td&gt;
&lt;td&gt;retrying the same invalid body&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;429&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;use bounded backoff and show availability state&lt;/td&gt;
&lt;td&gt;unlimited concurrent retries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;terminal task failure&lt;/td&gt;
&lt;td&gt;preserve task ID and reason&lt;/td&gt;
&lt;td&gt;reporting success because task creation returned an ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;authenticated or temporary content URL&lt;/td&gt;
&lt;td&gt;retrieve through the documented flow&lt;/td&gt;
&lt;td&gt;treating it as a permanent public asset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;unsupported region or account&lt;/td&gt;
&lt;td&gt;disable the action and explain the requirement&lt;/td&gt;
&lt;td&gt;implying universal availability&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A gateway does not remove upstream or model failure modes. It gives the&lt;br&gt;
application one public access boundary from which to handle them.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Add observability before adding more models
&lt;/h2&gt;

&lt;p&gt;For each request, consider recording:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an internal request ID with no customer secret;&lt;/li&gt;
&lt;li&gt;route and public model name;&lt;/li&gt;
&lt;li&gt;timestamp and application version;&lt;/li&gt;
&lt;li&gt;an input hash instead of private raw media where possible;&lt;/li&gt;
&lt;li&gt;task ID for asynchronous work;&lt;/li&gt;
&lt;li&gt;current state and terminal reason;&lt;/li&gt;
&lt;li&gt;output location and retention policy;&lt;/li&gt;
&lt;li&gt;retry count;&lt;/li&gt;
&lt;li&gt;review decision.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not log a full authorization header, API key, private signed URL, or&lt;br&gt;
unrestricted customer prompt by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Treat API success and production approval as separate gates
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;200&lt;/code&gt; response or a completed task proves that the technical request&lt;br&gt;
finished. It does not prove that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;product identity is accurate;&lt;/li&gt;
&lt;li&gt;text and claims are correct;&lt;/li&gt;
&lt;li&gt;the media is licensed for its intended use;&lt;/li&gt;
&lt;li&gt;the result fits the destination platform;&lt;/li&gt;
&lt;li&gt;retention and privacy requirements were met.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Merchant image and video workflows need a second approval gate for product&lt;br&gt;
truth, rights, output quality, and storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should you use an API instead of a packaged workflow?
&lt;/h2&gt;

&lt;p&gt;Use the raw API when your team wants to own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the user interface and database;&lt;/li&gt;
&lt;li&gt;the contract registry;&lt;/li&gt;
&lt;li&gt;task queues and retry behavior;&lt;/li&gt;
&lt;li&gt;storage and retention;&lt;/li&gt;
&lt;li&gt;approvals and cost controls;&lt;/li&gt;
&lt;li&gt;output QA and support.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use a packaged Commerce Skill when you want a repeatable workflow that&lt;br&gt;
coordinates multiple steps and reports its evidence and approval gates. A&lt;br&gt;
Skill does not eliminate model cost, rights review, or human judgment; it&lt;br&gt;
changes how the workflow is organized.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations to plan for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;One key does not mean one schema.&lt;/li&gt;
&lt;li&gt;Not every route is OpenAI-compatible.&lt;/li&gt;
&lt;li&gt;Model lists and account availability can change.&lt;/li&gt;
&lt;li&gt;Route registration does not prove every account can use every model.&lt;/li&gt;
&lt;li&gt;Price, credits, latency, throughput, and uptime need current first-party
checks.&lt;/li&gt;
&lt;li&gt;Generated or parsed media still needs rights and output review.&lt;/li&gt;
&lt;li&gt;A unified API does not guarantee lower cost or better reliability.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What does “unified AI API” mean?
&lt;/h3&gt;

&lt;p&gt;It means that selected task families share an access layer, base URL,&lt;br&gt;
authentication pattern, and route-discovery process. It should not mean that&lt;br&gt;
all models use one body or response schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does every endpoint use one request format?
&lt;/h3&gt;

&lt;p&gt;No. Image, video, speech, chat, and commerce routes have separate contracts.&lt;br&gt;
Validate only the fields documented for the selected model and route.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is every XPLA route OpenAI-compatible?
&lt;/h3&gt;

&lt;p&gt;No. The chat route follows the Chat Completions contract. Do not apply one&lt;br&gt;
global compatibility assumption to image, video, speech, or commerce routes.&lt;/p&gt;

&lt;h3&gt;
  
  
  How should an application handle video generation?
&lt;/h3&gt;

&lt;p&gt;Persist the task ID, poll at bounded intervals, stop on a terminal state, and&lt;br&gt;
retrieve content through the documented authenticated route.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does a unified API guarantee lower cost or higher uptime?
&lt;/h3&gt;

&lt;p&gt;No. Those claims require current pricing and controlled operational evidence.&lt;br&gt;
An access layer alone cannot prove them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://xplaai.com/en-us/api/" rel="noopener noreferrer"&gt;Maintained XPLA unified AI API guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://xplaai.com/en-us/api/ai-image/" rel="noopener noreferrer"&gt;AI image generation API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://xplaai.com/en-us/api/ai-video/" rel="noopener noreferrer"&gt;AI video API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://xplaai.com/en-us/skills/" rel="noopener noreferrer"&gt;Commerce Skills&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Disclosure: I prepared this tutorial for XPLA after rechecking the maintained guide and route contracts on September 1, 2026. I have not included a paid result, customer case, traffic claim, or ranking promise.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
