{"record":{"id":"e42c317cbb851129","repo":"calesthio/OpenMontage","slug":"files-api-did-not-return-an-upload-url","errorCode":null,"errorMessage":"Files API did not return an upload URL","messagePattern":"Files API did not return an upload URL","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"tools/video/gemini_omni_video.py","lineNumber":246,"sourceCode":"        video_bytes = path.read_bytes()\n\n        start_resp = requests_mod.post(\n            _UPLOAD_URL,\n            headers={\n                \"x-goog-api-key\": api_key,\n                \"X-Goog-Upload-Protocol\": \"resumable\",\n                \"X-Goog-Upload-Command\": \"start\",\n                \"X-Goog-Upload-Header-Content-Length\": str(len(video_bytes)),\n                \"X-Goog-Upload-Header-Content-Type\": mime_type,\n                \"Content-Type\": \"application/json\",\n            },\n            json={\"file\": {\"display_name\": path.name}},\n            timeout=30,\n        )\n        start_resp.raise_for_status()\n        upload_url = start_resp.headers.get(\"X-Goog-Upload-URL\")\n        if not upload_url:\n            raise RuntimeError(\"Files API did not return an upload URL\")\n\n        upload_resp = requests_mod.post(\n            upload_url,\n            headers={\n                \"X-Goog-Upload-Command\": \"upload, finalize\",\n                \"X-Goog-Upload-Offset\": \"0\",\n                \"Content-Length\": str(len(video_bytes)),\n            },\n            data=video_bytes,\n            timeout=300,\n        )\n        upload_resp.raise_for_status()\n        file_info = upload_resp.json().get(\"file\", {})\n\n        # Wait until the uploaded video is processed before referencing it.\n        deadline = time.time() + _MAX_POLL_SECONDS\n        while str(file_info.get(\"state\", \"\")).upper() == \"PROCESSING\":\n            if time.time() > deadline:","sourceCodeStart":228,"sourceCodeEnd":264,"githubUrl":"https://github.com/calesthio/OpenMontage/blob/95e1c3d0ab93482159818560f6a8c8e866b9139f/tools/video/gemini_omni_video.py#L228-L264","documentation":"Raised as RuntimeError when the Google Files API resumable-upload start request succeeds (2xx) but the response lacks the X-Goog-Upload-URL header needed for the subsequent upload+finalize POST. Without that header the protocol cannot proceed, so the tool aborts before sending video bytes.","triggerScenarios":"Transient API behavior or a proxy/CDN stripping the X-Goog-Upload-URL response header; an API version change where the Files API returns the URL in the body instead of the header; auth quota states that alter the start response shape.","commonSituations":"Corporate proxies (mitm/nginx) filtering unknown X- headers in both directions; intermittent Google-side issues where the start call 200s without a session; SDK/endpoint drift after Google changes the upload protocol.","solutions":["Retry the operation — transient start-session failures usually resolve immediately","If behind a proxy, ensure it forwards the X-Goog-* response headers, or bypass it for googleapis.com","Inspect start_resp.headers (and body) in a debug run to see whether the URL moved to the JSON body; if so the tool needs updating to read it","Check API key validity and Files API quota — some rejection paths can surface here"],"exampleFix":"# before (single attempt)\nresult = gemini_omni_video.run(inputs={'prompt':'...','video_path':vid})\n\n# after (bounded retry)\nfor attempt in range(3):\n    try:\n        result = gemini_omni_video.run(inputs={'prompt':'...','video_path':vid}); break\n    except RuntimeError as e:\n        if 'upload URL' not in str(e) or attempt == 2: raise\n        time.sleep(2 ** attempt)","handlingStrategy":"retry","validationCode":"# nothing caller-side can validate — but you can preflight reachability/headers\nimport requests\nr = requests.post('https://generativelanguage.googleapis.com/upload/v1beta/files',\n                  headers={'x-goog-api-key': api_key}, timeout=10)\n# if your proxy strips X-Goog-* headers, this surfaces it before the real run","typeGuard":null,"tryCatchPattern":"for attempt in range(3):\n    try:\n        result = gemini_omni_video.run(inputs=inputs)\n        break\n    except RuntimeError as e:\n        if 'upload URL' not in str(e) or attempt == 2:\n            raise\n        time.sleep(2 ** attempt)","preventionTips":["Retry transient upload-start failures with exponential backoff (2-3 attempts)","Ensure proxies pass X-Goog-* request and response headers through untouched","Keep the tool updated — if Google moves the URL into the body, the parser needs the change"],"tags":["gemini","files-api","upload","network","headers"],"backgroundTag":null,"analyzedSha":"95e1c3d0ab93482159818560f6a8c8e866b9139f","analyzedAt":"2026-08-15T06:31:20.014Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}