Make an avatar talk

November 4, 2026

Table of contents

  1. Request Headers
  2. Request Body
  3. Responses
  4. Model
  5. Examples
  6. Try It

Make a talking-head video of one avatar: the avatar faces the camera and says your script word for word, in its own voice, with matching lip movement. The video is an MP4 with sound, 1280×720 (landscape only, Google offers no portrait), and as long as the spoken script, up to 30 seconds.

How it differs from an avatar in POST /videos:

  POST /videos/avatar POST /videos with avatar_1
What you get A talking head reading your script A scene with action, where the avatar is a character
Length The length of the speech, up to 30 seconds 3 to 10 seconds
What the avatar says Exactly your script The lines you write in the prompt
Format 720p landscape 720p or 1080p, landscape or portrait
Allowance 1 AI avatar video (and 1 Gemini use for the speech) The clip’s seconds of video

Audio tags in square brackets, such as [whisper], [short pause] or [sarcasm], are performed, not read aloud. To hear how the avatar’s voice reads a script before you spend an avatar video, try it with POST /speech in the same voice.

The video runs on the account that holds the avatar. Its result is a video mediaId, which downloads with GET /media/mediaId and can be passed to POST /videos/extend, POST /videos/upscale and POST /videos/edit like any other clip.

Cost

  • Each video uses 1 AI avatar video from the account’s monthly allowance, shown as avatarVideo in GET /accounts/email. It does not use the account’s video seconds.
  • The speech is made first and uses 1 of the account’s monthly Gemini uses (speech), like POST /speech.
  • Google checks the video while making it: for safety, and for a picture that looks like a prominent person. This check is stricter than the one for avatar pictures, so an avatar made from a photo of someone who is not famous can still be refused here. When Google refuses, the job fails with 422 and Google still counts the video against the allowance. A script Google refuses before the video starts costs nothing.
  • A script that runs over 30 seconds when spoken fails with 400 before the video starts. The speech use is spent.

Sync, async and webhooks

Google takes about 1 to 2.5 minutes to make a talking avatar video. A sync request waits about 100 seconds and then answers 202 with the job still running, so plan to fetch the result with GET /jobs/jobid or a replyUrl webhook, or pass async: true.

  • Every generation runs as a job on our side, with up to 5 minutes for Google’s answer, so a slow answer is never lost.
  • Sync (the default): the POST waits for the job and answers 200 with the finished job record, or the job’s error code. A video usually takes 20 to 100 seconds, a talking avatar video 1 to 2.5 minutes, an image about 10 and speech a few. If the job is still running after about 100 seconds, the POST answers 202 with the job as it stands: fetch the result with GET /jobs/jobid. Always check status: only completed has a result.
  • Async: pass async: true. The POST answers 202 at once with the job in status: "processing". Poll GET /jobs/jobid or wait for the webhook.
  • replyUrl receives one POST of the final job record, as JSON, when the job completes or fails, for sync and async requests alike. It does not make a request async.
  • Each account runs at most maxJobs jobs at a time (default 3, range 1 to 10, set with POST /accounts). GET /jobs shows what is running. Job records are kept for 7 days.

https://api.useapi.net/v1/google-vids/videos/avatar

Request Headers
Authorization: Bearer {API token}
Content-Type: application/json
# Alternatively you can use multipart/form-data
# Content-Type: multipart/form-data
Request Body
{
  "avatar": "user:[email protected]:eyJnIjoiaDMy…",
  "script": "Hi there! Thanks for joining us today. Let me show you what we built this week.",
  "async": true,
  "replyUrl": "https://your-domain.com/webhook",
  "replyRef": "weekly-update-1"
}
  • avatar is required, an avatarId from POST /avatars or GET /avatars. The video runs on that avatar’s account.
  • script is required, what the avatar says, written as it should be spoken. Audio tags in square brackets are performed, not read. Spoken, it must fit in 30 seconds: depending on the voice that is about 250 to 400 characters, and [slow pace] or pauses make it longer (370 characters with [slow pace] ran 33 seconds in a calm voice).
    Length: 1 to 400 characters.
  • async is optional, true to answer 202 at once and run the job in the background. Default: false.
  • replyUrl is optional, a public http(s) URL that receives one POST of the job record when the job completes or fails. It works with sync and async requests.
    Callback body has the same JSON shape as GET /jobs/jobid response.
    Maximum length: 1024 characters.
  • replyRef is optional, your own reference echoed back in the job record.
    Maximum length: 1024 characters.

Any other parameter returns 400 Parameter <name> not supported.

Responses
  • 200 OK — the video is ready. In sync mode this is rare, since most videos take longer than the sync wait. GET /jobs/jobid returns the same record.

    {
      "jobid": "j1104215335919482713a-u12345-email:[email protected]:google-vids",
      "type": "avatarVideo",
      "email": "[email protected]",
      "status": "completed",
      "created": "2026-11-04T21:53:35.919Z",
      "request": {
        "avatar": "user:[email protected]:eyJnIjoiaDMy…",
        "script": "Hi there! Thanks for joining us today. Let me show you what we built this week.",
        "async": true
      },
      "updated": "2026-11-04T21:54:34.055Z",
      "completed": "2026-11-04T21:54:34.055Z",
      "result": {
        "mediaId": "user:[email protected]:eyJ1IjoiaHR0…",
        "width": 1280,
        "height": 720,
        "duration": 4,
        "resolution": "720p",
        "aspectRatio": "landscape",
        "model": "/flix/generate_avatar_video/v2_conditional",
        "speechDuration": 4.28,
        "quota": {
          "avatarVideo": {
            "limit": 10,
            "left": 9,
            "resetAt": "2026-12-01T08:00:00.000Z"
          }
        },
        "elapsedMs": 58136
      }
    }
    
  • 202 Accepted — the job is running: with async: true at once, in sync mode after about 100 seconds of waiting. Fetch the result with GET /jobs/jobid (or wait for the webhook).

    {
      "jobid": "j1104215335919482713a-u12345-email:[email protected]:google-vids",
      "type": "avatarVideo",
      "email": "[email protected]",
      "status": "processing",
      "created": "2026-11-04T21:53:35.919Z",
      "request": {
        "avatar": "user:[email protected]:eyJnIjoiaDMy…",
        "script": "Hi there! Thanks for joining us today. Let me show you what we built this week.",
        "async": true
      }
    }
    
  • 400 Bad Request — a parameter is missing or invalid, the avatar uses an older voice, the avatar’s account has no AI avatar videos on its plan (a monthly limit of 0), or the spoken script is longer than 30 seconds (the failed job record).

    {
      "error": "Parameter script length exceeds 400 characters",
      "code": 400
    }
    

    An avatar made in the Vids web app with one of Google’s older narrator voices cannot talk through the API. Make a new avatar with POST /avatars, for example from this one with avatar.

    {
      "error": "This avatar uses an older Vids narrator voice that the API cannot send. Make a new avatar with POST /avatars",
      "code": 400
    }
    

    A script that runs past 30 seconds when spoken:

    {
      "error": {
        "code": 400,
        "message": "The script runs 34.2 seconds when spoken in this voice. An avatar video takes up to 30 seconds of speech (Google's limit): shorten the script"
      }
    }
    

    The body is the failed job record, shortened here to its error.

  • 401 Unauthorized

    Invalid API token.

    {
      "error": "useapi.net ⁝ Unauthorized",
      "code": 401
    }
    
  • 403 Forbidden — the avatarId was issued to a different API token, Google answered that the account’s plan has no AI avatar videos, or the account has Google Workspace smart features turned off (the failed job record, error.code: 403).

    {
      "error": "avatar id does not belong to this API token",
      "code": 403
    }
    
  • 404 Not Found — the account that holds the avatar is not connected.

    {
      "error": "Account [email protected] (from the avatar id) is not configured",
      "code": 404
    }
    
  • 422 Unprocessable Content — Google refused the script, or refused the video while making it (its safety check, or an avatar picture that looks like a prominent person). A refused script costs nothing. A refused video still counts against the account’s AI avatar videos. Change the script or use another avatar and try again.

    {
      "error": {
        "code": 422,
        "message": "Google's safety check refused the finished avatar video (FINISH_REASON_GENERATED_VIDEO_SAFETY). Google still counts it against the account's monthly AI avatar allowance. Change the script or the avatar and try again"
      }
    }
    

    An avatar picture Google takes for a prominent person:

    {
      "error": {
        "code": 422,
        "message": "Google refused to animate this avatar: its picture looks like a prominent person (FINISH_REASON_INPUT_IMAGE_CELEBRITY). Google still counts it against the account's monthly AI avatar allowance. Use an avatar of someone else"
      }
    }
    

    The body is the failed job record, shortened here to its error.

  • 429 Too Many Requests — the account is running maxJobs jobs, its AI avatar videos or Gemini uses for this month are used up, or Google is limiting the account for a while.

    {
      "error": "Account [email protected] is running 3 of 3 jobs (maxJobs). Wait for one to finish, or raise maxJobs with POST /accounts",
      "code": 429
    }
    

    Once Google refuses a job because the account’s monthly AI avatar video allowance is used up, the API keeps Google’s reset time on the account (exhaustedUntil in GET /accounts) and gives it no AI avatar video jobs until then. A request that must run on that account is answered at once with 429, with no job created and no call to Google. retryAt is when to try again. If the account’s plan changed, GET /accounts/email reads its allowance again and lets it take jobs right away.

    {
      "error": "Account [email protected] has used up this month's Google Vids AI avatar video allowance. It resets at 2026-11-01T07:00:00.000Z. If its plan changed, call GET /accounts/[email protected] to refresh it",
      "code": 429,
      "retryAt": "2026-11-01T07:00:00.000Z"
    }
    

    A job that Google itself refuses for its allowance (the first refusal, or a job that needs more than the account has left) fails with error.code 429, error.retryAt set to the reset time, and error.quota with what was left (limit, left, resetAt). An account with some allowance left still takes smaller jobs.

  • 502 Bad Gateway — Google could not make the video and gave another reason, or answered in a way the API could not read. The body is the failed job record, and error.message has Google’s reason.

  • 503 Service Unavailable — Google answered with a server error or could not be reached, or the async job could not be queued. Retry shortly. The body is the failed job record.

  • 504 Gateway Timeout — Google did not finish the video within the job’s time budget. It may still count against the account’s AI avatar videos: run it again.

    {
      "error": {
        "code": 504,
        "message": "Google did not finish the avatar video within the job's time budget. It may still count against the account's AI avatar allowance: run it again"
      }
    }
    

    The body is the failed job record, shortened here to its error.

  • 596 Account Error

    Google reported the account as signed out. While we re-check it, the answer is the first message below. Retry in about a minute. If it stays signed out, the account is paused, you receive an email, and the answer becomes the second message until you re-add it via Setup Google Vids.

    {
      "error": "Google reported this account as signed out. We are re-checking it — please retry in about a minute. If it stays signed out, you will receive a re-add email.",
      "code": 596
    }
    
    {
      "error": "Account [email protected]: Google signed this account out. Re-add it at https://useapi.net/docs/start-here/setup-google-vids",
      "code": 596
    }
    
Model

The job record, type: "avatarVideo". GET /jobs/jobid and the replyUrl webhook return the same shape. The result’s duration is the video’s length in whole seconds and speechDuration the exact length of the spoken script. The codes are listed on GET /jobs/jobid.

{ // TypeScript, all fields are optional
  jobid: string                  // j<time><random><v|i|s|a>-u<id>-email:<email>-bot:google-vids
  type: 'video' | 'image' | 'speech' | 'avatarVideo'
  mode?: 'text' | 'image' | 'ingredients' | 'extend' | 'upscale' | 'edit'   // video jobs only
  email: string                  // the account the job runs on
  status: 'processing' | 'completed' | 'failed'
  created: string                // ISO 8601, when the job was accepted
  updated?: string               // ISO 8601, the last status change
  completed?: string             // ISO 8601, when the job completed or failed
  request: Record<string, unknown>   // your request body as sent (ids as strings)
  replyUrl?: string
  replyRef?: string
  error?: {                      // status 'failed'
    code: number                 // 400 | 403 | 404 | 422 | 429 | 500 | 502 | 503 | 504 | 596, see GET /jobs/{jobid}
    message: string
    retryAt?: string             // code 429: ISO 8601, when to try again
    quota?: {                    // code 429 for allowance: Google's counter for this kind of job when it refused it
      limit: number              // monthly limit (seconds of video, images, Gemini uses or AI avatar videos)
      left: number               // what was left, 0 when used up
      resetAt: string            // ISO 8601, when the allowance resets
    }
  }
  result?: {                     // status 'completed'
    mediaId: string              // download it with GET /media/{mediaId}. A video: extend / upscale / edit it. An image: use it as startImage, referenceImage_N or avatar image. Speech: a WAV
    width?: number               // video and image
    height?: number              // video and image
    duration?: number            // video: length of the whole clip in whole seconds. Speech: length of the audio in seconds
    resolution?: '720p' | '1080p'              // video
    aspectRatio?: 'landscape' | 'portrait'     // video
    speechDuration?: number      // avatarVideo: length of the spoken script in seconds
    model: string | null         // Google's backend path, e.g. /flix/generate_videos_omni_t2v_psq/v1
    quota?: {                    // what the account has left after this job (absent when Google sends no counter)
      video?: { limit: number, left: number, resetAt: string }   // seconds, video jobs
      image?: { limit: number, left: number, resetAt: string }   // images, image jobs
      speech?: { limit: number, left: number, resetAt: string }  // Gemini uses, speech jobs; absent on plans Google does not meter (Ultra)
      avatarVideo?: { limit: number, left: number, resetAt: string }   // AI avatar videos, avatarVideo jobs
    }
    elapsedMs: number            // how long Google took
  }
}
Examples
  • curl -X POST "https://api.useapi.net/v1/google-vids/videos/avatar" \
       -H "Content-Type: application/json" \
       -H "Authorization: Bearer …" \
       -d '{
         "avatar": "user:[email protected]:eyJnIjoiaDMy…",
         "script": "Hi there! Thanks for joining us today. Let me show you what we built this week.",
         "async": true
       }'
    
  • const token = "API token";
    const apiUrl = "https://api.useapi.net/v1/google-vids";
    const headers = { "Content-Type": "application/json", "Authorization": `Bearer ${token}` };
    
    const response = await fetch(`${apiUrl}/videos/avatar`, {
      method: "POST",
      headers,
      body: JSON.stringify({
        avatar: "user:[email protected]:eyJnIjoiaDMy…",
        script: "Hi there! Thanks for joining us today. Let me show you what we built this week.",
        async: true
      })
    });
    let job = await response.json();
    console.log("submitted", response.status, job);
    
    while (job.status === "processing") {
      await new Promise(r => setTimeout(r, 15000));
      job = await (await fetch(`${apiUrl}/jobs/${encodeURIComponent(job.jobid)}`, { headers })).json();
      console.log(job.status);
    }
    if (job.status === "completed") {
      const video = await fetch(`${apiUrl}/media/${encodeURIComponent(job.result.mediaId)}`, { headers });
      console.log("video bytes", (await video.arrayBuffer()).byteLength);
    } else
      console.log("error", job.error);
    
  • import time
    import requests
    from urllib.parse import quote
    
    token = "API token"
    apiUrl = "https://api.useapi.net/v1/google-vids"
    headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}"}
    
    data = {
        "avatar": "user:[email protected]:eyJnIjoiaDMy…",
        "script": "Hi there! Thanks for joining us today. Let me show you what we built this week.",
        "async": True
    }
    job = requests.post(f"{apiUrl}/videos/avatar", headers=headers, json=data).json()
    print("submitted", job)
    
    while job.get("status") == "processing":
        time.sleep(15)
        job = requests.get(f"{apiUrl}/jobs/{quote(job['jobid'], safe='')}", headers=headers).json()
        print(job["status"])
    
    if job.get("status") == "completed":
        video = requests.get(f"{apiUrl}/media/{quote(job['result']['mediaId'], safe='')}", headers=headers)
        with open("avatar.mp4", "wb") as f:
            f.write(video.content)
    else:
        print("error", job.get("error"))
    
Try It

A sync request waits about 100 seconds and usually answers 202: then paste the jobid into GET /jobs/jobid a minute later to see the video.