yikart/AiToEarn · error
Failed to fetch image: ${response.status} ${response.statusT
Error message
Failed to fetch image: ${response.status} ${response.statusText} What it means
fetchImageAsBase64 downloads an image from a URL (used by gemini.service imageData to hydrate image inputs) and throws when the HTTP response is not ok (response.ok false), embedding status and statusText. This is a straightforward network/HTTP failure fetching remote image bytes.
Source
Thrown at project/aitoearn-backend/apps/aitoearn-ai/src/core/ai/libs/gemini/gemini.service.ts:207
else if (modality === MediaModality.IMAGE) {
result.image = (result.image || 0) + tokenCount
}
else if (modality === MediaModality.AUDIO) {
result.audio = (result.audio || 0) + tokenCount
}
else if (modality === MediaModality.VIDEO) {
result.video = (result.video || 0) + tokenCount
}
}
return Object.keys(result).length > 0 ? result : undefined
}
private async fetchImageAsBase64(url: string): Promise<{ base64: string, mimeType: string }> {
this.logger.debug({ url }, 'Fetching image as base64')
const response = await fetch(url)
if (!response.ok) {
throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`)
}
const contentType = response.headers.get('content-type') || 'image/jpeg'
const buffer = Buffer.from(await response.arrayBuffer())
return {
base64: buffer.toString('base64'),
mimeType: contentType,
}
}
async generateContent(params: GenerateContentParameters): Promise<GenerateContentResponse> {
return this.withAvailability('generateContent', async () => {
const resolvedParams = await this.resolveRelayJson(params)
return await this.genAiClient.models.generateContent(resolvedParams)
}, params.model)
}
async generateContentStream(params: GenerateContentParameters): Promise<AsyncGenerator<GenerateContentResponse>> {
const resolvedParams = await this.resolveRelayJson(params)View on GitHub (pinned to d3aa8bea5b)
Solutions
- Verify the URL is publicly fetchable (curl -I the URL from the AI service host)
- Re-generate signed URLs before use or extend expiry
- Serve the image from storage the backend can reach, or send the image as base64 data URI instead of a URL
- Add retry with backoff for transient 5xx/network failures, and map this error to a clear user-facing message
Example fix
// before
await geminiService.generateImage({ prompt, imageUrls: [expiredPresignedUrl] })
// after
const freshUrl = await storage.getSignedUrl(key, { expiresIn: 3600 })
await geminiService.generateImage({ prompt, imageUrls: [freshUrl] }) Defensive patterns
Strategy: retry
Validate before calling
async function assertFetchable(url: string): Promise<void> {
const res = await fetch(url, { method: 'HEAD' })
if (!res.ok) throw new Error(`Image URL not fetchable: ${res.status} ${res.statusText}`)
} Try / catch
try {
const img = await geminiService.generateImage({ imageUrls: [url] })
} catch (e) {
if (e instanceof Error && e.message.startsWith('Failed to fetch image:')) {
// refresh signed URL or fall back to base64 input
const fresh = await storage.getSignedUrl(key, { expiresIn: 3600 })
return geminiService.generateImage({ imageUrls: [fresh] })
}
throw e
} Prevention
- Generate signed URLs shortly before use with adequate expiry
- Verify URLs are reachable from the backend network (not just the client)
- Prefer sending images as base64/data URIs for small images
- Check user-supplied URLs respond 200 and are public before submitting
When it happens
Trigger: Image URL returns 404/403/410, requires auth or signed access that expired, host is unreachable/DNS fails upstream of fetch (fetch itself may reject first), server returns 5xx, or a redirect to a blocked/expired resource.
Common situations: Expired pre-signed S3/OSS URLs; user-supplied image URLs pointing to private or deleted resources; hotlink protection; internal URLs not reachable from the AI service's network.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
- S3DownloadFileFailed
- ResponseCode.S3DownloadFileFailed
- ResponseCode.S3DownloadFileFailed
- Failed to fetch ${url}: ${response.status}
- HTTP error! status: ${response.status}
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/35b9c23f029b7400.
Report an issue: GitHub.