{"record":{"id":"8e4fa96b9b5a40db","repo":"affaan-m/ECC","slug":"no-url-in-provider-response-response-shape-may-have-changed-8e4fa9","errorCode":null,"errorMessage":"no URL in provider response; response shape may have changed (provider payload omitted)","messagePattern":"no URL in provider response; response shape may have changed \\(provider payload omitted\\)","errorType":"exception","errorClass":"FalError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/taste/falapi.py","lineNumber":370,"sourceCode":"        elif isinstance(node, dict):\n            if isinstance(node.get(\"url\"), str):\n                found.append(node[\"url\"])\n            for k, v in node.items():\n                if k != \"url\":\n                    walk(v)\n        elif isinstance(node, (list, tuple)):\n            for v in node:\n                walk(v)\n\n    walk(result)\n    seen: set[str] = set()\n    return [u for u in found if not (u in seen or seen.add(u))]\n\n\ndef first_url(result: Any, endpoint: str) -> str:\n    urls = parse_urls(result)\n    if not urls:\n        raise FalError(\n            \"no URL in provider response; response shape may have changed \"\n            \"(provider payload omitted)\"\n        )\n    return urls[0]\n\n\ndef _mesh_url(result: Any, endpoint: str) -> str:\n    \"\"\"The GLB out of a 3D response, addressed by key rather than by position.\n\n    ``first_url`` would work only as long as ``model_glb`` happens to be the\n    first URL-bearing key in the response. It is today; the response also\n    carries a ``thumbnail`` PNG and a ``model_urls`` block with obj/fbx/mtl,\n    so a key reordering upstream would quietly start returning a preview image\n    where a mesh is expected - and a preview image downloads fine, so nothing\n    would fail until Blender refused to open it.\n    \"\"\"\n    if isinstance(result, dict):\n        for path in ((\"model_glb\", \"url\"), (\"model_urls\", \"glb\", \"url\"),","sourceCodeStart":352,"sourceCodeEnd":388,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/taste/falapi.py#L352-L388","documentation":"falapi.parse_urls found no downloadable asset URL in the JSON returned by a fal.ai endpoint, so first_url cannot return a result and raises FalError. The library treats this as a sign that the provider's response schema changed or the model produced no output. It is a defensive guard against silently returning an empty/None URL to callers like _mesh_url, retopologize, split_parts, images_to_video, reference_to_video, and extract_frame.","triggerScenarios":"Any wrapper call (_mesh_url, retopologize, split_parts, images_to_video, reference_to_video, extract_frame, merge_videos) whose fal.ai endpoint returns 200 with a payload containing no URL fields (e.g. missing 'video_url'/'image_url'/'mesh_url' keys), an empty output object, or a qualitatively failed generation that still reports success.","commonSituations":"fal.ai changes an endpoint's response schema after a model upgrade; the queue request succeeded but the model returned no artifact (e.g. safety filter rejected the generation); calling extract_frame with a timestamp beyond the video length; an API version mismatch between the client payload and the deployed model.","solutions":["Print/log the raw provider response (result) to inspect the actual keys and find where the URL now lives","Check fal.ai docs/changelog for the endpoint used in ENDPOINTS and update parse_urls to the new response shape","Retry the generation — transient model failures can return an empty payload; also verify the input passed safety/size checks","Pin the model endpoint version on fal.ai so schema changes don't silently break the client"],"exampleFix":"// before\nurl = first_url(result, ENDPOINTS['images_to_video'])\n// after\nurls = parse_urls(result)\nif not urls:\n    print(json.dumps(result, indent=2))  # inspect real payload keys\n    raise FalError('images_to_video returned no URL; inspect payload above')","handlingStrategy":"try-catch","validationCode":"urls = parse_urls(result)\nif not urls:\n    # decide fallback before calling first_url\n    ...","typeGuard":"def has_urls(result) -> bool:\n    return bool(parse_urls(result))","tryCatchPattern":"try:\n    url = first_url(result, endpoint)\nexcept FalError:\n    logger.warning('no asset URL for %s; retrying', endpoint)\n    url = first_url(submit(endpoint, payload, timeout), endpoint)  # one retry","preventionTips":["Log the raw provider payload on every wrapper call for post-mortems","Pin fal.ai model endpoint versions to avoid silent schema drift","Add a unit test asserting parse_urls against a captured real response","Treat empty generations (content-filter rejections) as an expected case in pipeline code"],"tags":["network","api","response-shape","fal"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}