3 days trying to solve: Veo 3.0 403 error Generative Language API has not been used in project 542708778979 etc

Hiya! having a persistent issue already for 3 Days :dotted_line_face::

I’m using Supabase Deno / Edge Functions:

  1. I’ve called the Veo 3.0 model with the prompt, done the longrun operations successfully 1of 1, 1of 2, etc… until 1 of 7 more or less. Then got the VideoFile object as follows:

“Generated video file: {\n uri: "https://generativelanguage.googleapis.com/v1beta/files/ojhax2nibn8k:download?alt=media\”,\n videoBytes: undefined,\n mimeType: undefined\n}\n"

But when I tried to downoad as per the documentation generatedVideo.video it nevers downloads anything. all the console logs below // Download the video file that you can see on the code below are undefied.

Also when I post the URL on a webBrowser this is what I see (Attached- I don’t recognise this project)

This is the whole generate-ai-vIdeos edge function code running on Supabase:

import { serve } from '…
import { createClient } from …
import { GoogleGenAI } from “npm:@google/genai”

const corsHeaders = {
‘Access-Control-Allow-Origin’: ‘*’,
‘Access-Control-Allow-Headers’: ‘authorization, x-client-info, apikey, content-type’,
‘Access-Control-Allow-Methods’: ‘POST, OPTIONS’,
}

interface VideoFormat {
count: number;
dimensions: {
width: number;
height: number;
};
aspectRatio: string;
}

interface VideoObject {
prompt: string;
postType: string;
videoFormats: {
landscape: VideoFormat;
portrait: VideoFormat;
square: VideoFormat;
};
}

interface VideoGenerationRequest {
videoObject: VideoObject;
}

serve(async (req) => {
if (req.method === ‘OPTIONS’) {
return new Response(null, { headers: corsHeaders })
}

try {
const authHeader = req.headers.get(‘Authorization’)
if (!authHeader) {
throw new Error(‘No authorization header’)
}

const supabaseClient = createClient(
  Deno.env.get('SUPABASE_URL') ?? '',
  Deno.env.get('SUPABASE_ANON_KEY') ?? ''
)

const { data: { user }, error: authError } = await supabaseClient.auth.getUser(
  authHeader.replace('Bearer ', '')
)

if (authError || !user) {
  throw new Error('Unauthorized')
}

const supabaseAdmin = createClient(
  Deno.env.get('SUPABASE_URL') ?? '',
  Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
)

const { videoObject }: VideoGenerationRequest = await req.json()

if (!videoObject || !videoObject.prompt) {
  throw new Error('Video object with prompt is required')
}

console.log('=== VIDEO GENERATION REQUEST ===')
console.log('User ID:', user.id)
console.log('Prompt:', videoObject.prompt)
console.log('Video formats requested:', {
  landscape: videoObject.videoFormats.landscape.count,
  portrait: videoObject.videoFormats.portrait.count,
  square: videoObject.videoFormats.square.count
})
console.log('=== END VIDEO GENERATION REQUEST ===')

// Get the Gemini API key
const geminiApiKey = Deno.env.get('GEMINI_API_KEY')
if (!geminiApiKey) {
  throw new Error('Gemini API key not configured')
}

// Initialize Google GenAI
const ai = new GoogleGenAI({
  apiKey: geminiApiKey
})

const generatedVideos = []
const errors = []

// Process each video format
for (const [formatName, formatData] of Object.entries(videoObject.videoFormats)) {
  if (formatData.count === 0) continue

  console.log(`Generating ${formatData.count} ${formatName} videos...`)

  // Generate the specified number of videos for this format
  for (let i = 0; i < formatData.count; i++) {
    try {
      console.log(`Generating ${formatName} video ${i + 1}/${formatData.count}`)

      // Create database record first
      const { data: videoRecord, error: recordError } = await supabaseAdmin
        .from('ai_generated_videos')
        .insert({
          user_id: user.id,
          prompt: videoObject.prompt,
          video_format: formatName,
          aspect_ratio: formatData.aspectRatio,
          width: formatData.dimensions.width,
          height: formatData.dimensions.height,
          model_used: 'veo-3.0-generate-preview',
          status: 'pending'
        })
        .select()
        .single()

      if (recordError) {
        console.error('Error creating video record:', recordError)
        errors.push(`Failed to create database record for ${formatName} video ${i + 1}`)
        continue
      }

      console.log('Created video record:', videoRecord.id)

      // Update status to processing
      await supabaseAdmin
        .from('ai_generated_videos')
        .update({ status: 'processing' })
        .eq('id', videoRecord.id)

      // Generate video using Veo 3
      console.log(`Calling Veo 3 API for ${formatName} video...`)
      
      let operation = await ai.models.generateVideos({
        model: "veo-3.0-generate-preview",
        prompt: videoObject.prompt,
        config: {
          aspectRatio: formatData.aspectRatio
        },
      })

      console.log('Video generation operation started:', operation.name)

      // Update database with operation ID
      await supabaseAdmin
        .from('ai_generated_videos')
        .update({ operation_id: operation.name })
        .eq('id', videoRecord.id)

      // Poll the operation status until the video is ready
      let pollCount = 0
      const maxPolls = 60 // Maximum 10 minutes (60 * 10 seconds)
      
      while (!operation.done && pollCount < maxPolls) {
        console.log(`Waiting for video generation to complete... (poll ${pollCount + 1}/${maxPolls})`)
        await new Promise((resolve) => setTimeout(resolve, 10000)) // Wait 10 seconds
        
        operation = await ai.operations.getVideosOperation({
          operation: operation,
        })
        
        pollCount++
      }

      if (!operation.done) {
        throw new Error('Video generation timed out after 10 minutes')
      }

      if (operation.error) {
        throw new Error(`Video generation failed: ${operation.error.message || 'Unknown error'}`)
      }

      console.log('Video generation completed successfully')

      // Get the generated video
      const generatedVideo = operation.response?.generatedVideos?.[0]
      if (!generatedVideo?.video) {
        throw new Error('No video data in response')
      }

      console.log('Generated video file:', generatedVideo.video)

      // Get user's bucket for storage
      const { data: userBucket, error: bucketError } = await supabaseAdmin
        .from('content_buckets')
        .select('bucket_name')
        .eq('user_id', user.id)
        .limit(1)
        .single()

      if (bucketError || !userBucket) {
        throw new Error('User bucket not found')
      }

      // Download and store the video
      const timestamp = Date.now()
      const randomId = Math.random().toString(36).substring(2, 15)
      const fileName = `ai-video-${formatName}-${timestamp}-${randomId}.mp4`
      const storagePath = `ai-generated-videos/${fileName}`

      console.log('Downloading video from Google AI...')
      
      // Download the video file
      const videoFile = await ai.files.download({
        file: generatedVideo.video
        
      })
      
      // Debug logging to understand videoFile object structure
      console.log('=== VIDEO FILE DEBUG INFO ===')
      console.log('Raw videoFile value:', videoFile)
      console.log('Downloaded video file type:', typeof videoFile)
      console.log('Is Uint8Array:', videoFile instanceof Uint8Array)
      console.log('Is Blob:', videoFile instanceof Blob)
      console.log('Size:', videoFile?.byteLength || videoFile?.size)
      //console.log('videoFile type:', typeof videoFile)
      //console.log('videoFile constructor:', videoFile?.constructor?.name)
      //console.log('videoFile instanceof Uint8Array:', videoFile instanceof Uint8Array)
      //console.log('videoFile instanceof ArrayBuffer:', videoFile instanceof ArrayBuffer)
      //console.log('videoFile instanceof Blob:', videoFile instanceof Blob)
      //console.log('videoFile properties:', Object.getOwnPropertyNames(videoFile))
      console.log('videoFile size property:', videoFile?.size)
      console.log('videoFile byteLength property:', videoFile?.byteLength)
      console.log('videoFile length property:', videoFile?.length)
      if (videoFile instanceof ArrayBuffer) {
        console.log('ArrayBuffer byteLength:', videoFile.byteLength)
      }
      if (videoFile instanceof Blob) {
        console.log('Blob size:', videoFile.size)
        console.log('Blob type:', videoFile.type)
      }
      console.log('=== END VIDEO FILE DEBUG INFO ===')

      console.log('Video downloaded, uploading to Supabase storage...')

      // Upload to Supabase storage
      const { data: uploadData, error: uploadError } = await supabaseAdmin.storage
        .from(userBucket.bucket_name)
        .upload(storagePath, videoFile, {
          contentType: 'video/mp4',
          upsert: false
        })

      if (uploadError) {
        throw new Error(`Failed to upload video: ${uploadError.message}`)
      }

      console.log('Video uploaded successfully:', uploadData.path)

      // Generate signed URL
      const { data: signedUrlData } = await supabaseAdmin.storage
        .from(userBucket.bucket_name)
        .createSignedUrl(storagePath, 3600) // 1 hour expiry

      // Update database record with completion
      const { error: updateError } = await supabaseAdmin
        .from('ai_generated_videos')
        .update({
          status: 'completed',
          video_url: signedUrlData?.signedUrl,
          storage_path: storagePath,
          file_size: videoFile?.size || videoFile?.byteLength || 0,
          updated_at: new Date().toISOString()
        })
        .eq('id', videoRecord.id)

      if (updateError) {
        console.error('Error updating video record:', updateError)
      }

      generatedVideos.push({
        id: videoRecord.id,
        format: formatName,
        video_url: signedUrlData?.signedUrl,
        storage_path: storagePath,
        dimensions: formatData.dimensions,
        aspect_ratio: formatData.aspectRatio
      })

      console.log(`Successfully generated ${formatName} video ${i + 1}/${formatData.count}`)

    } catch (error) {
      console.error(`Error generating ${formatName} video ${i + 1}:`, error)
      
      // Update database record with error
      const { data: failedRecord } = await supabaseAdmin
        .from('ai_generated_videos')
        .select('id')
        .eq('user_id', user.id)
        .eq('video_format', formatName)
        .eq('status', 'processing')
        .order('created_at', { ascending: false })
        .limit(1)
        .single()

      if (failedRecord) {
        await supabaseAdmin
          .from('ai_generated_videos')
          .update({
            status: 'failed',
            error_message: error.message || 'Unknown error',
            updated_at: new Date().toISOString()
          })
          .eq('id', failedRecord.id)
      }

      errors.push(`${formatName} video ${i + 1}: ${error.message}`)
    }
  }
}

console.log('=== VIDEO GENERATION SUMMARY ===')
console.log(`Successfully generated: ${generatedVideos.length} videos`)
console.log(`Errors: ${errors.length}`)
console.log('Generated videos:', generatedVideos)
console.log('Errors:', errors)
console.log('=== END VIDEO GENERATION SUMMARY ===')

return new Response(
  JSON.stringify({
    success: generatedVideos.length > 0,
    generated_videos: generatedVideos,
    total_generated: generatedVideos.length,
    errors: errors,
    message: `Successfully generated ${generatedVideos.length} videos${errors.length > 0 ? ` with ${errors.length} errors` : ''}`
  }),
  {
    headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    status: 200,
  }
)

} catch (error) {
console.error(‘Error in generate-ai-videos function:’, error)

return new Response(
  JSON.stringify({
    success: false,
    error: error.message || 'Internal server error'
  }),
  {
    headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    status: 400,
  }
)

}
})

same problem for all veo models, even the same “project 542708778979”

did u solve it?

Not yet @Rinat_Mullakhmetov , im thinking this generic error might be just a default access error and the real reason not share on the error. Still stuck on this part of teh project.

I moved to a different area of the project to see the issue again in a few days with fresh eyes, is quite frustrating when you can’t move forward on some points. But luckily im advancing on other areas of teh project.

Please do let me know if you solve it! i will have a look at it again in a couple of days

did you try this ?

Same issue. Trying everything, changed APIs, tried to change the link with my project ID, I have to go on production (and on vacation immediately later) with a Django project and I’m stuck with this thing that seems a bloody bug to me…
Anyone fixed this?

Hi everyone,

I think this issue occurs when you try to access the video via the default url. The default url points to a project which you do not have access to hence the 403. You need to add your api key to the URL to make it point to your project.

const url = decodeURIComponent(generatedVideo.video.uri);
const res = await fetch(${url}&key=${process.env.API_KEY});

Source: The Veo3 sample in AI Studio

2 Likes

Hi @Ibrahim_Adams, Thanks so much this solved the issue.

It is strange why this is so badly documented on the following link ( which are the docs I was following Generate videos with Veo 3  |  Gemini API  |  Google AI for Developers )

Where did you find this sample? can you share the Source: The Veo3 sample in AI Studio

I can confirm that this is now working after I changed this approach ( The one on the Veo 3 Docs):

// Download the video

ai.files.download({
    file: operation.response.generatedVideos[0].video,
    downloadPath: "veo3_with_image_input.mp4",
});
console.log(`Generated video saved to veo3_with_image_input.mp4`);

To this approach ( the suggestion from @Ibrahim_Adams )

// Fetch the video URI from Google AI with Gemini API key
          console.log('Fetching video URI from Google AI...')
          const url = decodeURIComponent(generatedVideo.video.uri);
          const videoUriResponse = await fetch(`${url}&key=${geminiApiKey}`);

          console.log(videoUriResponse)

2 MAIN CHANGES:

  1. I replaced download() for fetch ()
  2. I added my API to the url that I got from the Response

It looks like it solved. I’m almost crying, thank you very much!!

1 Like

This solves the issue! :folded_hands: I also found an example in the documentation where this is shown Generate videos with Veo 3 in Gemini API  |  Google AI for Developers

Hi Everyone, sorry about the delay, we’d love to fix this, but we’re having trouble reproducing it.

Getting that error is “normal” when pasting the URL with no key into a web-browser. But it should not be coming back from ai.files.download.

Is everyone hitting this error also using Supabase Deno / Edge functions like OP, or are people getting this in other environments too?

Anyone else have clues on how to reproduce this?

This might be fixed on our main branch with: fix: Fix downloader. With this change after you await the download, t… · googleapis/js-genai@127c9bf · GitHub

Does that fix it for anyone?

Sorry to necro a dead thread, but this is one of the top results for the project id. For what it’s worth, I also get this currently via the api and good old curl but for batches endpoint.

```
curl “https://generativelanguage.googleapis.com/download/v1beta/files/b$RESPONSE_FILESNAMEdownload?alt=media” -H “x-goog-api-key: $GEMINI_API_KEY”
```

This also explains why I was getting permission denied via the go sdk too. I tried to use the key= approach but still got the permission denied. Yes the api token is correct as I can get the status among other things just not download the results.

1 Like

Hi @Mark_Daoust, I’m facing the same issue here. Is there a way to get a signed URL for the video instead of downloading it? In serverless environments, saving to temp storage doesn’t really work. I was hoping to get a direct download URL from the API (like Replicate does) that I can safely use in the browser. Exposing the API key in the URL isn’t an option since end users need access. Any suggestions?