{"record":{"id":"9b89cf86757183c0","repo":"vercel/ai","slug":"failed-to-download-url-statuscode-statust","errorCode":null,"errorMessage":"Failed to download ${url}: ${statusCode} ${statusText}","messagePattern":"Failed to download (.+?): (.+?) (.+?)","errorType":"exception","errorClass":"DownloadError","httpStatus":null,"severity":"error","filePath":"packages/provider-utils/src/download-blob.ts","lineNumber":34,"sourceCode":" * @returns A Promise that resolves to the downloaded Blob.\n *\n * @throws DownloadError if the download fails or exceeds maxBytes.\n */\nexport async function downloadBlob(\n  url: string,\n  options?: { maxBytes?: number; abortSignal?: AbortSignal },\n): Promise<Blob> {\n  try {\n    const response = await fetchWithValidatedRedirects({\n      url,\n      abortSignal: options?.abortSignal,\n    });\n\n    if (!response.ok) {\n      // Release the connection before rejecting so an error status from an\n      // attacker-controlled origin cannot leak open sockets.\n      await cancelResponseBody(response);\n      throw new DownloadError({\n        url,\n        statusCode: response.status,\n        statusText: response.statusText,\n      });\n    }\n\n    const data = await readResponseWithSizeLimit({\n      response,\n      url,\n      maxBytes: options?.maxBytes ?? DEFAULT_MAX_DOWNLOAD_SIZE,\n    });\n\n    const contentType = response.headers.get('content-type') ?? undefined;\n    return new Blob([data], contentType ? { type: contentType } : undefined);\n  } catch (error) {\n    if (DownloadError.isInstance(error)) {\n      throw error;\n    }","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/provider-utils/src/download-blob.ts#L16-L52","documentation":"downloadBlob (used internally when the SDK fetches media files from URLs, e.g. provider-returned image/video URLs) wraps every non-ok HTTP response in a DownloadError carrying the status code and status text. The library cancels the body first to avoid leaking sockets, then throws so callers get a consistent error type for failed downloads.","triggerScenarios":"A file URL passed to the SDK (or a URL returned by a provider in a response) was fetched and the server replied with a non-2xx status — 404 for a deleted/expired asset, 403 for a URL requiring auth, 410 for expired signed URLs, 5xx for origin problems.","commonSituations":"Expired pre-signed S3/CDN URLs returned by a provider; private URLs fetched without credentials; typo'd baseURL or asset path; rate limiting (429) or temporary origin outages (502/503).","solutions":["Check the statusCode/statusText in the DownloadError: 403/401 means credentials, 404/410 means the URL expired or is gone.","If the provider returns expiring URLs, download the asset promptly after the response or re-request a fresh URL.","Retry with backoff for 429/5xx statuses; fail fast for 4xx client errors.","Verify the URL is reachable (curl -I) and that any required auth headers are supplied via the fetch option where supported."],"exampleFix":"// before: assuming every URL works\nconst blob = await downloadBlob(url);\n\n// after: handle status-based failures\ntry {\n  const blob = await downloadBlob(url, { abortSignal });\n} catch (e) {\n  if (DownloadError.isInstance(e) && e.statusCode === 403) {\n    url = await refreshSignedUrl(); // re-fetch expired URL\n    return downloadBlob(url);\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"async function assertUrlReachable(url: string): Promise<void> {\n  const res = await fetch(url, { method: 'HEAD' });\n  if (!res.ok) throw new Error(`URL not downloadable: ${res.status} ${res.statusText}`);\n}","typeGuard":"import { DownloadError } from '@ai-sdk/provider-utils';\nfunction isDownloadError(e: unknown): e is DownloadError {\n  return DownloadError.isInstance(e);\n}","tryCatchPattern":"try {\n  const blob = await downloadBlob(url);\n} catch (error) {\n  if (DownloadError.isInstance(error)) {\n    if (error.statusCode === 404 || error.statusCode === 410) {\n      // request a fresh asset URL from the provider\n    } else if (error.statusCode === 429 || error.statusCode >= 500) {\n      // retry with backoff\n    }\n  }\n  throw error;\n}","preventionTips":["Download provider-returned URLs promptly before pre-signed links expire.","Check statusCode on DownloadError to decide retry vs re-fetch vs fail.","Verify asset URLs with a HEAD request or curl -I when debugging.","Apply exponential backoff for 429/5xx statuses only."],"tags":["network","http","download","http-status"],"backgroundTag":"http-download-failed","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}