{"record":{"id":"236f5c589e09345c","repo":"chinabugotech/hutool","slug":"download-failed-with-status","errorCode":null,"errorMessage":"Download failed with status: ","messagePattern":"Download failed with status: ","errorType":"exception","errorClass":"AIException","httpStatus":null,"severity":"error","filePath":"hutool-ai/src/main/java/cn/hutool/ai/model/gemini/GeminiServiceImpl.java","lineNumber":145,"sourceCode":"\tpublic String getVideoOperation(String operationName) {\n\t\tString endPoint = \"/\" + operationName;\n\t\tfinal HttpResponse response = sendGet(endPoint);\n\t\treturn response.body();\n\t}\n\n\t@Override\n\tpublic void downLoadVideo(String videoUri, String filePath) {\n\t\tif (StrUtil.isBlank(videoUri)) {\n\t\t\tthrow new AIException(\"Video URI is empty\");\n\t\t}\n\t\tfinal HttpResponse response = HttpRequest.get(videoUri)\n\t\t\t.header(\"x-goog-api-key\", config.getApiKey())\n\t\t\t.setFollowRedirects(true)\n\t\t\t.executeAsync();\n\t\tif (response.isOk()) {\n\t\t\tresponse.writeBody(FileUtil.file(filePath));\n\t\t} else {\n\t\t\tthrow new AIException(\"Download failed with status: \" + response.getStatus());\n\t\t}\n\t}\n\n\t@Override\n\tpublic String textToSpeech(String prompt) {\n\t\tfinal Map<String, Object> paramMap = buildTextToSpeechRequestMap(prompt);\n\t\tfinal HttpResponse response = sendPost(getEndpoint(false), JSONUtil.toJsonStr(paramMap));\n\t\treturn response.body();\n\t}\n\n\t@Override\n\tpublic String textToSpeech(String prompt, String voice) {\n\t\tfinal Map<String, Object> voiceConfig = MapUtil.of(\"prebuilt_voice_config\", MapUtil.of(\"voice_name\", voice));\n\t\tconfig.putAdditionalConfigByKey(\"speech_config\", MapUtil.of(\"voice_config\", voiceConfig));\n\t\treturn this.textToSpeech(prompt);\n\t}\n\n\t@Override","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/chinabugotech/hutool/blob/8870454b2a0c29cc6ffd31dcf5667c8ceb2fc442/hutool-ai/src/main/java/cn/hutool/ai/model/gemini/GeminiServiceImpl.java#L127-L163","documentation":"Thrown by GeminiServiceImpl.downLoadVideo after the async GET of videoUri returns a non-2xx status (response.isOk() is false). The message includes the HTTP status code. Unlike the transport exceptions, this is an application-level failure: the URI was reachable but the server refused (auth, not-found, expired link, etc.). Auth uses the x-goog-api-key header.","triggerScenarios":"Invalid or unauthorized Gemini API key (x-goog-api-key rejected -> 403); the video URI has expired or was already consumed; the URI is malformed leading to a 404; the resource belongs to a different project/key.","commonSituations":"Downloadable Gemini video URIs are short-lived -- delaying the download past expiry yields 4xx; rotating the API key between generate and download causes a mismatch; key without Generative Language API access.","solutions":["Download immediately after the operation completes, before the URI expires.","Verify the API key (x-goog-api-key) is the same and still valid.","Inspect response.getStatus() in the message: 403 -> key/permission, 404 -> wrong/expired URI.","Re-run predictVideo + getVideoOperation to obtain a fresh URI if it expired."],"exampleFix":"// before\ngemini.downLoadVideo(staleOrSharedUri, \"/tmp/out.mp4\");\n// -> Download failed with status: 403\n\n// after\n// regenerate to get a fresh, same-key URI\nString op = gemini.predictVideo(\"a cat\");\nString uri = pollUntilDoneAndGetUri(gemini, op);\ngemini.downLoadVideo(uri, \"/tmp/out.mp4\");","handlingStrategy":"try-catch","validationCode":"// Same-key, fresh-URI check before download\nif (StrUtil.isBlank(config.getApiKey())) throw new IllegalStateException(\"key missing\");\nif (uriOlderThanMinutes(uri, 5)) throw new IllegalStateException(\"uri likely expired\");","typeGuard":null,"tryCatchPattern":"try {\n    gemini.downLoadVideo(uri, path);\n} catch (AIException e) {\n    String m = e.getMessage();\n    if (m.startsWith(\"Download failed with status\")) {\n        int status = Integer.parseInt(m.substring(m.lastIndexOf(' ')).trim());\n        if (status == 403 || status == 401) { /* fix key */ }\n        else if (status == 404) { /* uri expired/wrong -> regenerate */ }\n    }\n    throw e;\n}","preventionTips":["Download immediately after the operation completes; URIs expire.","Use the same API key for generate and download.","Confirm the key has Generative Language API access."],"tags":["gemini","video","http-status","authentication"],"backgroundTag":null,"analyzedSha":"8870454b2a0c29cc6ffd31dcf5667c8ceb2fc442","analyzedAt":"2026-08-14T04:01:12.892Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}