vercel/ai · error · DownloadError
Failed to download ${url}: ${statusCode} ${statusText}
Error message
Failed to download ${url}: ${statusCode} ${statusText} What it means
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.
Source
Thrown at packages/provider-utils/src/download-blob.ts:34
* @returns A Promise that resolves to the downloaded Blob.
*
* @throws DownloadError if the download fails or exceeds maxBytes.
*/
export async function downloadBlob(
url: string,
options?: { maxBytes?: number; abortSignal?: AbortSignal },
): Promise<Blob> {
try {
const response = await fetchWithValidatedRedirects({
url,
abortSignal: options?.abortSignal,
});
if (!response.ok) {
// Release the connection before rejecting so an error status from an
// attacker-controlled origin cannot leak open sockets.
await cancelResponseBody(response);
throw new DownloadError({
url,
statusCode: response.status,
statusText: response.statusText,
});
}
const data = await readResponseWithSizeLimit({
response,
url,
maxBytes: options?.maxBytes ?? DEFAULT_MAX_DOWNLOAD_SIZE,
});
const contentType = response.headers.get('content-type') ?? undefined;
return new Blob([data], contentType ? { type: contentType } : undefined);
} catch (error) {
if (DownloadError.isInstance(error)) {
throw error;
}View on GitHub (pinned to 69428b1f8b)
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.
Example fix
// before: assuming every URL works
const blob = await downloadBlob(url);
// after: handle status-based failures
try {
const blob = await downloadBlob(url, { abortSignal });
} catch (e) {
if (DownloadError.isInstance(e) && e.statusCode === 403) {
url = await refreshSignedUrl(); // re-fetch expired URL
return downloadBlob(url);
}
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
async function assertUrlReachable(url: string): Promise<void> {
const res = await fetch(url, { method: 'HEAD' });
if (!res.ok) throw new Error(`URL not downloadable: ${res.status} ${res.statusText}`);
} Type guard
import { DownloadError } from '@ai-sdk/provider-utils';
function isDownloadError(e: unknown): e is DownloadError {
return DownloadError.isInstance(e);
} Try / catch
try {
const blob = await downloadBlob(url);
} catch (error) {
if (DownloadError.isInstance(error)) {
if (error.statusCode === 404 || error.statusCode === 410) {
// request a fresh asset URL from the provider
} else if (error.statusCode === 429 || error.statusCode >= 500) {
// retry with backoff
}
}
throw error;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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).
Related errors
- Failed to fetch the response.
- The response body is empty.
- ${readErrorMessage({ value, status: response.status })}
- Tool relay ${schema.name} failed with ${res.status}: ${body.
- Failed to download ${url}: ${cause}
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/9b89cf86757183c0.
Report an issue: GitHub.