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

1 Like

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!!