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

  1. Verify the URL is publicly fetchable (curl -I the URL from the AI service host)
  2. Re-generate signed URLs before use or extend expiry
  3. Serve the image from storage the backend can reach, or send the image as base64 data URI instead of a URL
  4. 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

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


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/35b9c23f029b7400. Report an issue: GitHub.