{"slug": "before-i-call-gpt-image-2-i-validate-these-7-things", "title": "Before I Call GPT Image 2, I Validate These 7 Things", "summary": "An engineer at XPLA detailed a seven-check preflight validation workflow for GPT Image 2 API calls, emphasizing contract-based request validation, dimension conflict checks, and idempotency handling to prevent duplicate billable generations. The approach includes rejecting unsupported fields, storing product identity and allowed transformations, and persisting generation state to reconcile uncertain transport outcomes.", "body_md": "I used to debug a bad AI product image by rewriting the prompt. Now I check\n\nthe request contract first.\n\nA GPT Image 2 API request can be valid JSON and still be wrong for the selected\n\nmodel. A generic UI can leak a field from another provider. Two dimension\n\ncontrols can conflict. A timeout can turn one intended image into two\n\ngenerations. A technically successful output can still change the product.\n\nThis is the seven-check preflight I put in front of the generation call.\n\nDisclosure: I work on XPLA. The workflow below is an engineering pattern,\n\nnot a promise about price, access, speed or image quality.\n\nI do not let one universal form submit every visible field to every image\n\nmodel. The application selects a model contract first:\n\n``` js\nconst imageContracts = {\n  \"gpt-image-2\": {\n    endpoint: \"/v1/images/generations\",\n    allowed: new Set([\n      \"model\", \"prompt\", \"image\", \"images\",\n      \"size\", \"aspectRatio\", \"quality\", \"replyType\", \"n\"\n    ]),\n    forbidden: new Set([\"imageSize\"])\n  }\n};\n```\n\nThe UI should be generated from that record. A control the selected model does\n\nnot support should not appear.\n\nSilently removing an unsupported field creates plausible but uncontrolled\n\noutputs. The user chooses a setting, the backend drops it, a default is used,\n\nand everyone blames the model.\n\n``` js\nfunction rejectUnknownFields(body, contract) {\n  const errors = [];\n\n  for (const key of Object.keys(body)) {\n    if (!contract.allowed.has(key)) {\n      errors.push(`Unsupported field for ${body.model}: ${key}`);\n    }\n  }\n\n  for (const key of contract.forbidden) {\n    if (body[key] !== undefined) {\n      errors.push(`Forbidden field for ${body.model}: ${key}`);\n    }\n  }\n\n  return errors;\n}\n```\n\nAn explicit local error is easier to fix than an attractive image produced\n\nfrom the wrong request.\n\nAn image form may expose a ratio, a pixel size, a provider-specific size field\n\nand an orientation inferred from the prompt. I allow one supported decision:\n\n```\nfunction validateDimensions(body) {\n  if (body.imageSize !== undefined) {\n    return [\"imageSize is not valid for this standard contract\"];\n  }\n\n  const supplied = [\n    body.size !== undefined,\n    body.aspectRatio !== undefined\n  ].filter(Boolean).length;\n\n  return supplied > 1\n    ? [\"Choose size or aspectRatio, not both\"]\n    : [];\n}\n```\n\nI store the requested orientation beside the output so QA can verify it.\n\nA reference is more than a URL. I store the product identity and the allowed\n\ntransformation:\n\n```\n{\n  \"source_type\": \"merchant_upload\",\n  \"rights_state\": \"confirmed_for_internal_calibration\",\n  \"product_id\": \"merchant-sku-104\",\n  \"variant\": \"matte-black-500ml\",\n  \"must_preserve\": [\n    \"single bottle\",\n    \"matte black body\",\n    \"silver cap\",\n    \"existing label geometry\"\n  ],\n  \"allowed_changes\": [\n    \"background\",\n    \"surface\",\n    \"lighting direction\"\n  ]\n}\n```\n\nA marketplace image being public does not automatically grant permission to\n\ndownload, transform or use it in advertising.\n\nThis is the operational rule I care about most.\n\nIf a client times out, it may not know whether the server failed before or\n\nafter generation. Repeating the request can create another billable task.\n\nBefore submission, I persist:\n\n```\n{\n  \"generation_id\": \"img-job-20260902-001\",\n  \"intent\": \"new_calibration\",\n  \"request_hash\": \"sha256-of-normalized-request\",\n  \"state\": \"submitted\",\n  \"attempt\": 1,\n  \"result_state\": \"unknown\",\n  \"next_action\": \"check_before-repeating\"\n}\n```\n\nMy state transition is:\n\n``` php\ndraft\n  -> approved\n  -> submitted\n  -> completed\n  -> accepted | repair | rejected\n\nsubmitted\n  -> transport_unknown\n  -> reconcile before another generation\n```\n\nA new creative candidate receives a new generation ID. An uncertain transport\n\nresult enters reconciliation. I do not hide both actions behind one “Try\n\nagain” button.\n\nFor the first run I use:\n\nThe question is narrow: can the workflow preserve the product facts that\n\nmatter? More outputs do not improve the evidence if no one reviews them.\n\nMy QA table looks like this:\n\n| Check | Expected | Decision |\n|---|---|---|\n| Product count | one unit | accept / repair / reject |\n| Body color | matte black | accept / repair / reject |\n| Cap | silver | accept / repair / reject |\n| Label geometry | unchanged | accept / repair / reject |\n| Invented claims | none | accept / repair / reject |\n| Orientation | requested ratio | accept / repair / reject |\n| Crop safety | fully visible | accept / repair / reject |\n\n“Looks good” is not a release decision. An attractive output can still invent\n\na bundle, accessory, certification or quantity.\n\n``` js\nfunction preflightGPTImage2(body, rightsRecord) {\n  const contract = imageContracts[body.model];\n  const errors = [];\n\n  if (!contract) errors.push(`Unknown model contract: ${body.model}`);\n  if (!body.prompt?.trim()) errors.push(\"prompt is required\");\n  if (body.n !== undefined && body.n !== 1) {\n    errors.push(\"Use n: 1 and create separate intentional requests\");\n  }\n  if (body.replyType !== undefined && body.replyType !== \"json\") {\n    errors.push('replyType must be \"json\"');\n  }\n\n  if (contract) {\n    errors.push(...rejectUnknownFields(body, contract));\n    errors.push(...validateDimensions(body));\n  }\n\n  if ((body.image || body.images) &&\n      rightsRecord?.rights_state !== \"confirmed_for_internal_calibration\") {\n    errors.push(\"Reference-image rights are not confirmed\");\n  }\n\n  return { ok: errors.length === 0, errors };\n}\n```\n\nThis is only the request boundary. A production implementation still needs\n\nmedia-type, file-size, URL, privacy, account, policy and storage checks.\n\n| Failure class | Example | Default action |\n|---|---|---|\n| Client | empty prompt | fix locally |\n| Contract | unsupported field | reject before submission |\n| Access | unauthorized key | stop and fix access |\n| Transport | lost response | reconcile before repeating |\n| Provider | transient failure | bounded retry policy |\n| Safety | risky request | stop or revise |\n| Output QA | altered product | repair or reject |\n\nI keep the final order simple:\n\n``` php\ncontract -> preflight -> approval -> one calibration\n-> product QA -> intentional scale\n```\n\nThe current XPLA-specific request shape and model-name boundaries are\n\ndocumented in the [GPT Image 2 API guide](https://xplaai.com/en-us/api/gpt-image-2/).\n\nRecheck the live contract before production integration.", "url": "https://wpnews.pro/news/before-i-call-gpt-image-2-i-validate-these-7-things", "canonical_source": "https://dev.to/_86f41ebeb2cab43917bd42/before-i-call-gpt-image-2-i-validate-these-7-things-3f6h", "published_at": "2026-09-02 11:45:32+00:00", "updated_at": "2026-09-02 11:53:52.286490+00:00", "lang": "en", "topics": ["developer-tools", "ai-products", "ai-infrastructure"], "entities": ["XPLA", "GPT Image 2"], "alternates": {"html": "https://wpnews.pro/news/before-i-call-gpt-image-2-i-validate-these-7-things", "markdown": "https://wpnews.pro/news/before-i-call-gpt-image-2-i-validate-these-7-things.md", "text": "https://wpnews.pro/news/before-i-call-gpt-image-2-i-validate-these-7-things.txt", "jsonld": "https://wpnews.pro/news/before-i-call-gpt-image-2-i-validate-these-7-things.jsonld"}}