From 9c9c070fbd6f009c7539db1bfc519b8062bfd0ee Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Tue, 24 Feb 2026 17:08:53 -0800 Subject: [PATCH] fix: handle Printful v2 API list response format v2 returns {data: [...]} (array) not {data: {...}} (object). Also fixes mockup task polling to use ?id= query param and extracts mockup_url from nested catalog_variant_mockups structure. Co-Authored-By: Claude Opus 4.6 --- backend/app/api/designs.py | 15 ++++++--------- backend/app/pod/printful_client.py | 13 ++++++++++--- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/backend/app/api/designs.py b/backend/app/api/designs.py index d81cddf..4c6f88d 100644 --- a/backend/app/api/designs.py +++ b/backend/app/api/designs.py @@ -166,16 +166,13 @@ async def _get_printful_mockup(slug: str, product) -> bytes | None: if not mockups: return None - # v2 response: catalog_variant_mockups → each has mockup_url or - # placements[].mockup_url. Also check legacy "url" field. + # v2 response: catalog_variant_mockups[] → .mockups[] → .mockup_url mockup_url = None - for m in mockups: - mockup_url = m.get("mockup_url") or m.get("url") - if not mockup_url and "placements" in m: - for p in m["placements"]: - mockup_url = p.get("mockup_url") or p.get("url") - if mockup_url: - break + for variant_mockup in mockups: + for mockup in variant_mockup.get("mockups", []): + mockup_url = mockup.get("mockup_url") or mockup.get("url") + if mockup_url: + break if mockup_url: break diff --git a/backend/app/pod/printful_client.py b/backend/app/pod/printful_client.py index f3f36c4..e9031b2 100644 --- a/backend/app/pod/printful_client.py +++ b/backend/app/pod/printful_client.py @@ -138,7 +138,9 @@ class PrintfulClient: json=payload, ) resp.raise_for_status() - data = resp.json().get("data", {}) + # v2 returns {"data": [{ ... }]} — data is a list + raw_data = resp.json().get("data", []) + data = raw_data[0] if isinstance(raw_data, list) and raw_data else raw_data task_id = data.get("id") or data.get("task_key") or data.get("task_id") logger.info(f"Printful mockup task created: {task_id}") return str(task_id) @@ -151,11 +153,16 @@ class PrintfulClient: """ async with httpx.AsyncClient(timeout=15.0) as client: resp = await client.get( - f"{BASE_URL}/mockup-tasks/{task_id}", + f"{BASE_URL}/mockup-tasks", headers=self._headers, + params={"id": task_id}, ) resp.raise_for_status() - return resp.json().get("data", {}) + # v2 returns {"data": [{ ... }]} — data is a list + raw_data = resp.json().get("data", []) + if isinstance(raw_data, list) and raw_data: + return raw_data[0] + return raw_data if isinstance(raw_data, dict) else {} async def generate_mockup_and_wait( self,