Extend Videos
January 29, 2026 (July 27, 2026)
Table of contents
Extend a previously generated video with a new prompt. Creates an additional 8-second segment that continues from the last ~1 second of the source video.
- Video extension requires a paid Google AI subscription with remaining credits.
- The extended video inherits the aspect ratio from the source video (landscape or portrait).
- Extended videos have ~1 second overlap with the source video’s last frame. Use
trimStartin POST /videos/concatenate to remove overlap when combining clips.
https://api.useapi.net/v1/google-flow/videos/extend
Request Headers
Authorization: Bearer {API token}
Content-Type: application/json
API tokenis required, see Setup useapi.net for details.
Request Body
{
"mediaGenerationId": "user:12345-email:6a6f...-video:CAMaJDMx...",
"prompt": "The camera slowly pans right revealing a majestic waterfall"
}
mediaGenerationIdis required. ThemediaGenerationIdfrom a previously generated or extended video via POST /videos or POST /videos/extend. The source video must be Veo-generated —omni-flashvideos cannot be extended (use omni-flash V2V edit via POST /videos withreferenceVideo_1instead).promptis required. Text description for the video extension (what happens next).modelis optional. Video generation model (default:veo-3.1-fast). Supported values:veo-3.1-fast,veo-3.1-quality,veo-3.1-lite,veo-3.1-lite-low-priority(Ultra $199 only, no credits, lower priority).countis optional. Number of video variations to generate (1-4, default: 1). Each variation uses a different seed.seedis optional. Random seed for reproducible results. If not specified, a random seed is generated. Whencount> 1, seeds are incremented (seed, seed+1, seed+2, …).asyncis optional, enables fire-and-forget mode (default:false). Whentrue, returns immediately with201 Createdand job metadata. Poll GET /jobs/jobIdfor completion status.replyUrlis optional, webhook URL for job status callbacks. Receives POST requests with job status updates (created,started,completed,failed). The JSON payload shape matches GET /jobs/jobIdresponse.replyRefis optional, custom reference string passed back in webhook callbacks.captchaToken,captchaRetry,captchaOrderare optional, mutually exclusive captcha parameters. See Captcha Parameters for details.
Responses
-
Video extended successfully. All video data is in the
media[]array.{ "jobId": "j1737312345678v-u12345-email:jo***@gmail.com-bot:google-flow", "media": [ { "name": "a1d95d21-75d2-482d-a354-14ef8802ce66", "projectId": "9f63078c-...redacted...", "workflowId": "fa986834-...redacted...", "workflowStepId": "CAE", "mediaMetadata": { "createTime": "2026-05-20T23:14:02.931677Z", "mediaTitle": "The camera slowly pans right revealing a majestic waterfall", "requestData": { "videoGenerationRequestData": { "...": "..." }, "clientPlatform": "CLIENT_PLATFORM_WEB" }, "mediaStatus": { "mediaGenerationStatus": "MEDIA_GENERATION_STATUS_SUCCESSFUL" }, "visibility": "PRIVATE" }, "video": { "generatedVideo": { "seed": 987654321, "prompt": "The camera slowly pans right revealing a majestic waterfall", "model": "veo_3_1_t2v", "baseImageMediaGenerationId": "", "isLooped": false, "aspectRatio": "VIDEO_ASPECT_RATIO_LANDSCAPE" }, "dimensions": { "length": "8s" }, "operation": { "name": "a1d95d21-...redacted..." } }, "mediaGenerationId": "user:12345-email:6a6f...-video:a1d95d21-...redacted...", "videoUrl": "https://flow-content.google/video/a1d95d21-...redacted...?Expires=...", "thumbnailUrl": "https://flow-content.google/image/a1d95d21-...redacted...?Expires=..." } ], "remainingCredits": 18760, "captcha": { "service": "AntiCaptcha", "taskId": "abc123...", "durationMs": 3500, "attempts": [ { "service": "AntiCaptcha", "taskId": "abc123...", "durationMs": 3500, "success": true } ] } } -
Job created in async mode (
async: true). Video extension is processing in the background.Use GET /jobs/
jobIdto poll for completion status.{ "jobid": "j1737312345678v-u12345-email:jo***@gmail.com-bot:google-flow", "type": "video", "status": "created", "created": "2026-01-29T12:34:56.789Z", "request": { "async": true, "prompt": "The camera slowly pans right revealing a majestic waterfall", "mediaGenerationId": "user:12345-email:6a6f...-video:CAMaJDMx..." }, "response": { "captcha": { "service": "AntiCaptcha", "taskId": "14af1dbb-885c-4e25-8121-7a79489dfd0e", "durationMs": 5357 } } } -
Invalid request.
{ "error": "Error message", "code": 400 } -
Invalid API token.
{ "error": "Unauthorized" } -
Request rejected by Google — the reCAPTCHA token was outright rejected (not just scored low). After internal retries with fresh tokens, the worker returns this. The top-level
errorfield carries acaptcha_quality:prefix so tooling can detect this failure class. IncreasecaptchaRetry(default 3) in the request body to 5 or higher, and configure additional providers via POST /accounts/captcha-providers socaptchaOrdercan cycle across them. If issues persist, contact your reCAPTCHA provider(s) support. -
Account not found or video not found.
{ "error": "Google Flow account [email protected] not found" } -
Video extension polling timeout (after 10 minutes).
{ "error": "Polling timeout" } -
Google returns several
RESOURCE_EXHAUSTED429 variants. Check thereasonfield and respond accordingly:Reason Meaning Scope What to do Cooldown PUBLIC_ERROR_USER_REQUESTS_THROTTLEDPer-user concurrency limit (the token was fine) All models on the account Run fewer parallel requests, retry later ~30 min PUBLIC_ERROR_USER_QUOTA_REACHEDAccount’s overall Flow quota cap All models on the account Add accounts, or omit emailto load-balance~30 min PUBLIC_ERROR_PER_MODEL_DAILY_QUOTA_REACHEDPer-model daily quota Only that model, on that account Switch model, or wait Next UTC midnight PUBLIC_ERROR_UNUSUAL_ACTIVITY_TOO_MUCH_TRAFFICCaptcha-provider issue — low reCAPTCHA score (worker already auto-retried) Per request Add more captcha providers + captchaOrder, or raisecaptchaRetry60s Every
429returns the cooldown two ways — an HTTPRetry-After: <seconds>header and a bodyretryAfter: <ISO timestamp>field. Honor it before retrying.When
emailis omitted, the first three reasons quarantine the account and the load balancer routes around it. If every account is quarantined for the model you get429witherror: "no_eligible_account". Routing details: GET /jobs › Load Balancing Algorithm.Captcha-quality failures prefix the top-level
errorwithcaptcha_quality:(e.g."captcha_quality: PUBLIC_ERROR_UNUSUAL_ACTIVITY_TOO_MUCH_TRAFFIC after 3 attempts") so you can detect them without parsing the nested body. -
503has two distinct causes — check the response body to tell them apart.A transient Google-side outage. Wait 5-10 seconds and retry.
{ "error": { "code": 503, "message": "Service temporarily unavailable.", "status": "UNAVAILABLE" } }A captcha-provider failure — the top-level
erroris prefixedCaptcha service failed:. The problem is your captcha provider (check its API key and balance), not Google. Don’t retry until the provider is back.{ "error": "Captcha service failed: ERROR_ZERO_BALANCE", "code": 503 }
Model
-
Video extension completed. Returns extended video data with signed URLs.
{ jobId: string // Job identifier media: Array<{ // Preferred — use this instead of operations name: string // Media identifier (raw UUID) projectId: string workflowId?: string workflowStepId?: string // e.g. "CAE" mediaMetadata: { createTime?: string // ISO 8601 timestamp mediaTitle?: string // Extension prompt requestData?: object // Full Google Flow request payload mediaStatus: { mediaGenerationStatus: string // MEDIA_GENERATION_STATUS_SUCCESSFUL | FAILED error?: { code: number; message: string } } visibility?: string } video?: { generatedVideo: { seed: number prompt?: string // Extension prompt model: string // veo_3_1_t2v baseImageMediaGenerationId?: string // Source video media ID isLooped: boolean aspectRatio: string // VIDEO_ASPECT_RATIO_LANDSCAPE | PORTRAIT } dimensions?: { length?: string } // Duration string (e.g. "8s") operation: { name: string } } mediaGenerationId: string // Encoded reference ID for use in subsequent API calls videoUrl?: string // Signed video download URL (MP4, valid ~6h) thumbnailUrl?: string // Signed thumbnail download URL (JPEG, valid ~6h) }> remainingCredits?: number captcha?: { // Captcha metadata service: string // "CapSolver" | "AntiCaptcha" | "YesCaptcha" | "CapMonster" | "SolveCaptcha" | "2Captcha" | "EzCaptcha" | "UserProvided" taskId?: string durationMs: number attempts: Array<{ service: string taskId?: string durationMs: number success: boolean error?: string }> } } -
Job created and processing in background. Structure matches GET /jobs/
jobIdresponse.{ jobid: string // Job identifier type: 'video' // Job type status: 'created' // Job status created: string // ISO 8601 timestamp request: { async: true prompt: string // Extension prompt mediaGenerationId: string // Source video reference model?: string // "veo-3.1-fast" seed?: number replyUrl?: string replyRef?: string } response: { captcha?: { // Present when captcha was used service: string // Provider or "UserProvided" taskId?: string // Absent for UserProvided durationMs: number } } } -
Error response structure.
{ jobId?: string // Present for job-related errors error: string // Error summary message code?: number // HTTP status code response?: { // API response with error details error?: { code: number message: string status: string details?: Array<{ '@type': string reason: string }> } } }
Missing media URLs
Media URL fields are optional and can be absent even when generation succeeded. When videoUrl, thumbnailUrl, fifeUrl, servingBaseUri, previewUrl or audioUrl is missing, the media itself is fine — only the download link could not be produced at that moment.
This is rare. Across the seven days of request logs we keep, it happened on one day only — twice, for about four hours and about forty minutes — and not at all on the other six. Google issues those links from an endpoint that limits how often it will answer a given network address, so it is Google limiting our outbound calls rather than useapi.net limiting you, and nothing you do triggers it. While it lasts it affects every account generating at that moment, then clears on its own. Your generation still completed and was charged as usual.
On a media item that generated successfully — mediaStatus.mediaGenerationStatus is MEDIA_GENERATION_STATUS_SUCCESSFUL — a missing URL means “not yet”, not “failed”, so read the field before using it and treat its absence as a retry condition. A media item that failed generation never carries a URL, and retrying will not produce one.
const url = media.videoUrl;
if (!url) {
// Not an error — the link is not available yet. See the options below.
}
How to get the link
Which route to use depends on whether you can wait.
If you are already polling the job, do nothing special. Completed video jobs re-resolve missing URLs automatically on GET /jobs/jobId, and the recovered link is saved, so a later poll returns it with no extra work on your side. This applies to video jobs created within the last 24 hours. Once the job is completed and a URL is still missing, leave at least a minute between polls — a job re-resolves at most once a minute, and the block clears only once traffic stops, so a tight retry loop lengthens the wait rather than shortening it.
If you hold a mediaGenerationId but no longer have the job, ask for that one media again with GET /assets/mediaGenerationId. That endpoint says which case you are in rather than leaving you to guess. A 503 with a Retry-After header means the link is coming and tells you how long to wait, so wait that long and ask again. A 404 means Google has no such media, and no amount of retrying will change it.
If you cannot wait, download the video through us with GET /assets/mediaGenerationId?raw=true. It reads the media over a different Google route that the block does not touch, so it works immediately and keeps working while the other two return nothing. It is video only.
Prefer the two link-based routes when you can. They hand you a link and you download straight from Google. The raw form moves the whole file through useapi.net on every call, which makes it a way out of a stuck download rather than your normal download path.
Characters and voices resolve their URLs as they are read, so simply requesting GET /characters/ref or GET /voices/ref again is enough. Wait a minute before retrying — a response that came back without its URL is held briefly, so an immediate repeat returns the same one.
None of this applies to images. Whenever an image URL is missing, POST /images puts the image itself in the response instead, base64-encoded in encodedImage — so you already have the picture and there is nothing to retry.
Examples
-
# First generate a video curl -X POST \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"prompt": "A serene mountain landscape at sunset"}' \ "https://api.useapi.net/v1/google-flow/videos" > generate.json # Extract mediaGenerationId from the response MEDIA_ID=$(jq -r '.media[0].mediaGenerationId' generate.json) # Extend the video curl -X POST \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d "{\"mediaGenerationId\": \"$MEDIA_ID\", \"prompt\": \"The camera slowly pans right revealing a majestic waterfall\"}" \ "https://api.useapi.net/v1/google-flow/videos/extend" > extend.json # Extract extended video URL and download VIDEO_URL=$(jq -r '.media[0].videoUrl' extend.json) curl "$VIDEO_URL" --output extended_video.mp4 -
const token = 'YOUR_API_TOKEN'; // First generate a video const generateResponse = await fetch('https://api.useapi.net/v1/google-flow/videos', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: 'A serene mountain landscape at sunset' }) }); const generated = await generateResponse.json(); // Prefer media[] over deprecated operations[] const mediaGenerationId = generated.media[0].mediaGenerationId; // Extend the video const extendResponse = await fetch('https://api.useapi.net/v1/google-flow/videos/extend', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ mediaGenerationId, prompt: 'The camera slowly pans right revealing a majestic waterfall' }) }); const extended = await extendResponse.json(); // Prefer media[] over deprecated operations[] const videoUrl = extended.media[0].videoUrl; // Download extended video (Node.js) const videoResponse = await fetch(videoUrl); const videoBuffer = await videoResponse.arrayBuffer(); require('fs').writeFileSync('extended_video.mp4', Buffer.from(videoBuffer)); console.log('Extended video saved!'); -
import requests token = 'YOUR_API_TOKEN' headers = { 'Authorization': f'Bearer {token}', 'Content-Type': 'application/json' } # First generate a video generate_response = requests.post( 'https://api.useapi.net/v1/google-flow/videos', headers=headers, json={'prompt': 'A serene mountain landscape at sunset'} ) generated = generate_response.json() # Prefer media[] over deprecated operations[] media_generation_id = generated['media'][0]['mediaGenerationId'] # Extend the video extend_response = requests.post( 'https://api.useapi.net/v1/google-flow/videos/extend', headers=headers, json={ 'mediaGenerationId': media_generation_id, 'prompt': 'The camera slowly pans right revealing a majestic waterfall' } ) extended = extend_response.json() # Prefer media[] over deprecated operations[] video_url = extended['media'][0]['videoUrl'] # Download extended video video_response = requests.get(video_url) with open('extended_video.mp4', 'wb') as f: f.write(video_response.content) print('Extended video saved!')