{"record":{"id":"10a43f2ea44a2881","repo":"docling-project/docling","slug":"artifact-download-failed-with-http-response-statu","errorCode":null,"errorMessage":"Artifact download failed with HTTP {response.status_code}.","messagePattern":"Artifact download failed with HTTP (.+?)\\.","errorType":"http","errorClass":"ArtifactDownloadError","httpStatus":null,"severity":"error","filePath":"docling/service_client/client.py","lineNumber":1899,"sourceCode":"        \"\"\"Download an external presigned artifact safely (sync).\n\n        Uses a dedicated client so the service ``X-Api-Key`` header is never sent\n        to the (external) artifact storage endpoint, validates every hop against\n        the SSRF guard, and enforces a streamed size cap. Redirects are followed\n        manually so each target can be re-validated.\n        \"\"\"\n        timeout = httpx.Timeout(self._artifact_download_timeout)\n        try:\n            with httpx.Client(timeout=timeout, follow_redirects=False) as client:\n                url = uri\n                for _ in range(MAX_ARTIFACT_DOWNLOAD_REDIRECTS + 1):\n                    self._validate_artifact_url(url)\n                    with client.stream(\"GET\", url) as response:\n                        if response.is_redirect:\n                            url = self._next_redirect_url(url, response)\n                            continue\n                        if response.status_code != 200:\n                            raise ArtifactDownloadError(\n                                \"Artifact download failed with HTTP \"\n                                f\"{response.status_code}.\"\n                            )\n                        chunks: list[bytes] = []\n                        total = 0\n                        for chunk in response.iter_bytes():\n                            total += len(chunk)\n                            self._check_artifact_size(total)\n                            chunks.append(chunk)\n                        return b\"\".join(chunks)\n                raise ArtifactDownloadError(\n                    \"Too many redirects while downloading artifact.\"\n                )\n        except httpx.HTTPError as exc:\n            raise ArtifactDownloadError(f\"Artifact download failed: {exc}\") from exc\n\n    async def _download_artifact_bytes_async(self, uri: str) -> bytes:\n        timeout = httpx.Timeout(self._artifact_download_timeout)","sourceCodeStart":1881,"sourceCodeEnd":1917,"githubUrl":"https://github.com/docling-project/docling/blob/61d76f1ff3f8428065465889f7b4577da7df704c/docling/service_client/client.py#L1881-L1917","documentation":"Raised as ArtifactDownloadError by the synchronous _download_artifact_bytes when the artifact server answers with a non-200, non-redirect status (403 expired presigned URL, 404 deleted object, 500, etc.). Each redirect hop is re-validated and followed manually; any final status other than 200 fails. Normally converted into a FAILURE ConversionResult by the materialization path.","triggerScenarios":"convert()/convert_all() materializing an artifact whose presigned URL has expired (S3/Xpiry headers), the object was deleted, or the endpoint returns 4xx/5xx.","commonSituations":"Long queue times so presigned URLs expire before download; clock skew; object lifecycle rules deleting artifacts; load balancer errors on the artifact host.","solutions":["Retry the conversion — expired presigned URLs are transient and a new task gets a fresh URL","Increase artifact_download_timeout and check network reachability to the object-store host if status is 5xx","Check object-store lifecycle/retention settings if 404s recur (artifacts cleaned before retrieval)"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"def is_artifact_download_error(exc: BaseException) -> bool:\n    return isinstance(exc, ArtifactDownloadError)","tryCatchPattern":"if res.status == ConversionStatus.FAILURE:\n    if any('HTTP 403' in (e.error_message or '') for e in res.errors):\n        res = retry_conversion(source)  # fresh presigned URL","preventionTips":["Retry conversions on 403 — presigned URL expiry is transient","Keep queue waits short enough that URLs do not expire before download"],"tags":["network","http-status","presigned-url","artifact-download"],"backgroundTag":null,"analyzedSha":"61d76f1ff3f8428065465889f7b4577da7df704c","analyzedAt":"2026-08-14T23:53:18.727Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}