{"record":{"id":"402d759befbe5ca0","repo":"calesthio/OpenMontage","slug":"atlas-cloud-upload-returned-no-url-str-data-50","errorCode":null,"errorMessage":"Atlas Cloud upload returned no URL: {str(data)[:500]}","messagePattern":"Atlas Cloud upload returned no URL: (.+?)","errorType":"exception","errorClass":"AtlasError","httpStatus":null,"severity":"error","filePath":"tools/atlas_client.py","lineNumber":216,"sourceCode":"        raise AtlasError(f\"Cannot upload — file not found: {path}\")\n\n    try:\n        with path.open(\"rb\") as handle:\n            response = requests.post(\n                UPLOAD_MEDIA_ENDPOINT,\n                headers=_headers(api_key, json_body=False),\n                files={\"file\": (path.name, handle)},\n                timeout=timeout,\n            )\n    except Exception as exc:  # noqa: BLE001\n        raise AtlasError(f\"Uploading {path.name} to Atlas Cloud failed: {exc}\") from exc\n\n    _raise_for_status(response, \"Atlas Cloud upload\")\n    data = _payload_of(response)\n\n    url = data.get(\"download_url\") or data.get(\"url\")\n    if not url:\n        raise AtlasError(f\"Atlas Cloud upload returned no URL: {str(data)[:500]}\")\n    return str(url)\n\n\ndef download(url: str, output_path: str | Path, timeout: int = 300) -> Path:\n    \"\"\"Download a generated asset to disk and return the written path.\"\"\"\n    import requests\n\n    try:\n        response = requests.get(url, timeout=timeout)\n    except Exception as exc:  # noqa: BLE001\n        raise AtlasError(f\"Downloading Atlas Cloud output failed: {exc}\") from exc\n\n    _raise_for_status(response, \"Atlas Cloud output download\")\n\n    path = Path(output_path)\n    path.parent.mkdir(parents=True, exist_ok=True)\n    path.write_bytes(response.content)\n    return path","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/calesthio/OpenMontage/blob/95e1c3d0ab93482159818560f6a8c8e866b9139f/tools/atlas_client.py#L198-L234","documentation":"Raised by upload_media when the upload request and envelope parsing succeeded but the payload contains neither download_url nor url — the two documented shapes for the hosted-asset response. The server accepted the file but the response lacks any retrievable location, so the caller cannot proceed to use it as a reference URL. The truncated payload is included.","triggerScenarios":"Atlas API drift adding a new field name for the hosted URL; the endpoint returning an acknowledgment-only envelope ({code:200, data:{}}) because the upload silently failed server-side; routing the upload to a different endpoint version that uses another schema.","commonSituations":"Client library out of date relative to the live Atlas API; regional endpoint variants with different response fields; server-side storage failures that still return success envelopes.","solutions":["Log the included payload snippet to see which field (if any) carries the URL, then extend the data.get('download_url') or data.get('url') chain in the client","Retry the upload once — empty data on success envelopes is often transient","Confirm you are hitting the documented UPLOAD_MEDIA_ENDPOINT for your Atlas API version","Report to Atlas support if code 200 with no URL persists on their current endpoint"],"exampleFix":"// before\nurl = data.get(\"download_url\") or data.get(\"url\")\n\n// after — tolerate a third documented shape\nurl = (\n    data.get(\"download_url\")\n    or data.get(\"url\")\n    or data.get(\"file\", {}).get(\"url\")\n)","handlingStrategy":"validation","validationCode":null,"typeGuard":"def upload_payload_has_url(data: dict) -> bool:\n    return bool(data.get(\"download_url\") or data.get(\"url\"))","tryCatchPattern":"try:\n    url = atlas_client.upload_media(path, api_key)\nexcept AtlasError as e:\n    if \"returned no URL\" in str(e):\n        time.sleep(3)\n        url = atlas_client.upload_media(path, api_key)  # empty-data envelopes are often transient\n    else:\n        raise","preventionTips":["Keep the client's accepted field list (download_url/url) in sync with Atlas docs","Log the payload snippet when this fires to identify renamed fields fast","Contract-test the upload response shape in CI with a recorded fixture"],"tags":["atlas-cloud","upload","api-contract","validation"],"backgroundTag":null,"analyzedSha":"95e1c3d0ab93482159818560f6a8c8e866b9139f","analyzedAt":"2026-08-15T06:31:20.014Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}