Generate Videos
November 17, 2025 (September 11, 2026)
Table of contents
- Model Capabilities
- Content Moderation
- Why a generation failed
- Request Headers
- Request Body
- Responses
- Model
- Missing media URLs
- Examples
- Try It
Generate videos using Google Flow AI models (Veo 3.1 Quality, Veo 3.1 Fast, Veo 3.1 Lite, Veo 3.1 Lite Lower Priority, Omni 1.1 Flash) from text prompts with optional start/end frames, reference images, reference video (Omni 1.1 Flash V2V edit), and voice narration. Videos are returned as signed URLs ready for download.
This endpoint features dynamic concurrency management. Video generation typically completes within 60-180 seconds depending on the model and mode. The endpoint automatically polls for completion and returns the final result.
Veo 3.1 Lite Lower Priority is available only to Google AI Ultra $199 subscribers — no credit cost but lower generation priority. The cheaper Ultra $99 plan does not include it.
Model Capabilities
Two model families: Veo 3.1 (four variants) and Omni 1.1 Flash (one).
The omni-flash model value is Gemini Omni Flash, which the Flow interface labels Omni 1.1 Flash since Google’s August 26, 2026 update. The model value stays omni-flash across Google’s version renames so existing integrations keep working — the current label is always available as displayName on GET /accounts/email.
Models
Veo figures: Google Flow — Credits and pricing. Omni figures measured against the live API.
| Model | Generation type(s) | Credits per generation |
|---|---|---|
Veo 3.1 - Liteveo-3.1-lite | 4s, 6s, 8s videos Extend | Non-Ultra: 10 Ultra: 5 |
Veo 3.1 - Lite Lower Priorityveo-3.1-lite-low-priority | 4s, 6s, 8s videos Extend | Ultra $199 only: 0 (lower priority) |
Veo 3.1 - Fast (default)veo-3.1-fast | 4s, 6s, 8s videos Extend | Non-Ultra: 20 Ultra: 10 |
Veo 3.1 - Qualityveo-3.1-quality | 8s videos Extend | 100 |
Omni 1.1 Flashomni-flash | 4s, 6s, 8s, 10s videos at 720p (does not include edits to uploaded & generated videos) | Plus / Pro / Ultra: 4s: 7 6s: 10 8s: 12 10s: 15 |
Omni 1.1 Flashomni-flash + resolution: 360p | 4s, 6s, 8s, 10s videos at 360p | Plus / Pro / Ultra: 4s: 4 6s: 5 8s: 6 10s: 7 |
Omni 1.1 Flashomni-flash + referenceVideo_1 | Edit uploaded & generated videos of all lengths | Plus / Pro / Ultra: 20 at 720p, 10 at 360p |
Aspect ratio (aspectRatio)
| Model | Supported values |
|---|---|
| All Veo variants | landscape (default), portrait, 1:1, 4:3, 3:4 |
omni-flash | landscape (default), portrait |
Duration (duration, sec)
| Mode / Model | Supported |
|---|---|
| Veo T2V / I2V / I2V-FL | 4, 6, 8 (default) — 4/6 are Ultra-only |
| Veo R2V | 8 only |
omni-flash T2V / R2V / I2V / I2V-FL | 4, 6, 8 (default), 10 |
omni-flash V2V edit | not accepted — output matches input trim window (max 10 s, set via endFrameIndex_1) |
References
| Reference | Parameter(s) | Veo (all variants) | omni-flash |
|---|---|---|---|
| Start frame (I2V) | startImage | ✓ | ✓ (the frame only — no referenceImage_*, no character_*, no referenceAudio_*) |
| Start + end frames (I2V-FL) | startImage + endImage | ✓ | ✓ (frames only — no referenceImage_*, no character_*, no referenceAudio_*) |
| Image refs (R2V / Ingredients) | referenceImage_1..7 | up to 3 (slots _1..3); not on veo-3.1-quality | up to 7 (R2V) / up to 5 (V2V edit) |
| Characters (R2V / Ingredients) | character_1..7 (from POST /characters) | 3 total with referenceImage_* — Veo fast/lite/lite-low-priority only, 8 s only, not on veo-3.1-quality | 7 total with referenceImage_* (R2V) |
| Voice narration | referenceAudio_1..5 (POST /voices refs OR system voice names) | 1 (slot _1, requires an image ref or character, R2V only); not on veo-3.1-quality | up to 5 (R2V, requires an image ref or character) / up to 3 (V2V edit) |
| Source video (V2V edit) | referenceVideo_1 (+ optional startFrameIndex_1 / endFrameIndex_1) | — | ✓ (uploaded MP4, trim on a 24 fps virtual timeline, max 240 frames = 10 s) |
Content Moderation
If your generation is moderated, retrying with the same prompt often succeeds on subsequent attempts — moderation decisions can vary between requests. Modifying the prompt, removing reference images, or swapping models can also help, as each has different content-moderation behavior.
One case does not improve on retry. IP_PROHIBITED means Google’s intellectual-property classifier flagged an input image as containing copyrighted, branded or recognizable content, and since the image is unchanged a retry gets flagged again. Replace the reference image instead — original photos of non-famous subjects pass where celebrity photos, film stills, product shots and copyrighted characters do not. Despite how it reads, the “IP” here is intellectual property and has nothing to do with network addresses.
Why a generation failed
When every operation in a job fails, the top-level error reads All operations failed for all of them. The reason is in response.failureReasons, a de-duplicated list of everything Google said about the failed media.
Two kinds of string arrive in it, and a job can carry either or both. Terminal codes name what went wrong, and the ones seen most are PUBLIC_ERROR_UNSAFE_GENERATION, PUBLIC_ERROR_PROMINENT_PEOPLE_FILTER_FAILED, PUBLIC_ERROR_AUDIO_FILTERED, PUBLIC_ERROR_MINOR, PUBLIC_ERROR_SEXUAL, PUBLIC_ERROR_DANGER_FILTER and PUBLIC_ERROR_IP_INPUT_IMAGE. Classifier labels name which filter fired, such as IP_PROHIBITED. The list is Google’s, not ours, so treat it as open-ended and match on substrings rather than on an exhaustive set.
The field is absent when Google returned no reason at all, which is common enough to code for — around a third of V2V edit failures arrive with nothing attached. An absent failureReasons is not an error in itself, and those jobs are usually worth one retry.
A job that failed before any operation started — a rejected request, a moderated prompt — never reaches this path. Those return the Google error verbatim instead, as shown in the 400 responses above.
https://api.useapi.net/v1/google-flow/videos
Request Headers
Authorization: Bearer {API token}
Content-Type: application/json
# Alternatively you can use multipart/form-data
# Content-Type: multipart/form-data
API tokenis required, see Setup useapi.net for details.
Request Body
{
"prompt": "A serene mountain landscape at sunset with camera slowly panning right",
"model": "veo-3.1-fast",
"aspectRatio": "landscape",
"duration": 8,
"count": 2,
"seed": 123456
}
Parameters are grouped below. For per-model limits and modes see Model Capabilities above.
Core
promptis required, text description for video generation.modelis optional, the AI model to use for video generation (default:veo-3.1-fast). Supported values:veo-3.1-quality,veo-3.1-fast,veo-3.1-lite,veo-3.1-lite-low-priority,omni-flash. See Model Capabilities.aspectRatiois optional, output video aspect ratio (default:landscape). Supported values:landscape,portrait. Veo also accepts1:1,4:3,3:4; Omni 1.1 Flash does not.durationis optional, output video length in seconds (default:8). Supported values:4,6,8,10. See Model Capabilities for per-model availability. Not accepted by Omni 1.1 Flash V2V edit (output length matches the input trim window).resolutionis optional, output resolution (default:720p). Supported values:360p,720p.omni-flashonly — Veo publishes no 360p variant, so360pon a Veo model is rejected rather than silently generating 720p at full price. A 360p generation costs roughly half the credits of the same request at 720p and can be promoted afterwards with POST /videos/upscale.countis optional, number of video variations to generate (1-4, default:1).seedis optional, random seed for reproducible results (integer ≥ 0).
Account selection
-
emailis optional, the Google Flow account to use.When only one account is configured, the API will automatically use that account.
With multiple accounts configured, omitting the email parameter triggers automatic load balancing based on video generation job statistics to select the healthiest account.
If
startImage,endImage,referenceImage_*, orreferenceVideo_1are provided, theemailparameter can be omitted — the API will automatically use the same account where the references were uploaded.
Image references
startImageis optional, mediaGenerationId from POST /assets/emailfor I2V mode (video starts with this frame). Supported on Veo and Omni 1.1 Flash, both of which also acceptendImage.endImageis optional, mediaGenerationId from POST /assets/emailfor I2V-FL mode (video ends with this frame, requiresstartImage). End-frame-only is not supported. Onomni-flashthe two frames are the only accepted input, soreferenceImage_*,character_*andreferenceAudio_*are rejected alongside them.referenceImage_1toreferenceImage_7are optional, mediaGenerationId from POST /assets/emailfor R2V mode. Slots_1.._3work on Veo (max 3, notveo-3.1-quality) and Omni 1.1 Flash; slots_4.._7areomni-flashonly. Cannot be combined withstartImage/endImage.
Characters
character_1tocharacter_7are optional, character reference-ids from POST /characters. Triggers R2V (Ingredients) mode just likereferenceImage_*does. Each character’s image count rolls into the model’s image-ref budget (see Model Capabilities › References):- Veo 3.1 Fast / Lite / Lite Lower Priority (
veo-3.1-fast,veo-3.1-lite,veo-3.1-lite-low-priority) — characters supported in 8 s only;referenceImages.length + Σ(character.imgs) ≤ 3. - Omni 1.1 Flash (
omni-flash) — characters supported in all durations;referenceImages.length + Σ(character.imgs) ≤ 7. - Veo 3.1 Quality (
veo-3.1-quality) — characters NOT supported (no Ingredients mode).
- Veo 3.1 Fast / Lite / Lite Lower Priority (
Voice narration
referenceAudio_1toreferenceAudio_5are optional. Each slot accepts either a system voice name (case-insensitive, one of the 30 presets) or a user voice from POST /voices. Per-model limits are in the References table above.
System voice presets (30 names):
[
"Achird", "Achernar", "Algieba", "Algenib", "Alnilam",
"Aoede", "Autonoe", "Callirrhoe", "Charon", "Despina",
"Enceladus", "Erinome", "Fenrir", "Gacrux", "Iapetus",
"Kore", "Laomedeia", "Leda", "Orus", "Puck",
"Pulcherrima", "Rasalgethi", "Sadachbia", "Sadaltager", "Schedar",
"Sulafat", "Umbriel", "Vindemiatrix", "Zephyr", "Zubenelgenubi"
]
System voice samples: https://www.gstatic.com/aitestkitchen/voices/samples/{Name}.wav
To list user (custom) voices on an account: GET /voices?email=…&source=user. To preview a single voice (system or user): GET /voices/ref.
Video reference (Omni 1.1 Flash V2V edit)
referenceVideo_1is optional (Omni 1.1 Flash only), mediaGenerationId of a video uploaded via POST /assets/email, or of a video this API generated or edited earlier. Switches the request into V2V edit mode. Cannot be combined withstartImage/endImage.startFrameIndex_1is optional (Omni 1.1 Flash V2V only, paired withreferenceVideo_1), trim start on a 24 fps virtual timeline. Range: 0-239. Default:0.endFrameIndex_1is optional (Omni 1.1 Flash V2V only, paired withreferenceVideo_1), trim end on a 24 fps virtual timeline. Range: 1-240 (240 = 10 s, the V2V input maximum). Default: the end of the clip, or 240 for clips longer than 10 s. Must be greater thanstartFrameIndex_1.
An edit works on a slice of the input video, and startFrameIndex_1 / endFrameIndex_1 pick that slice. Google counts 24 frames per second, so frame 24 is one second in, frame 120 is five seconds in, and frame 240 is ten seconds in. The output is that slice re-rendered with your edit, so it is exactly as long as the slice. There is no duration parameter on an edit.
| Input clip | You send | Slice used | Output |
|---|---|---|---|
| 10 s | nothing | frames 0 to 240 | 10 s |
| 10 s | endFrameIndex_1: 120 | frames 0 to 120 | 5 s |
| 13 s | nothing | frames 0 to 240 | 10 s, the last 3 s of the input are not used |
| 8 s | nothing | frames 0 to 192 | 8 s |
An edit does not add time to a video. The motion follows the source closely: in our tests the edit reproduced the source’s first and last frames almost exactly, and a prompt asking it to begin elsewhere was ignored. Feeding the output back in as the next referenceVideo_1 with a new line to say gives you a new take of the same seconds, with the voice and the gestures carried over from the source.
That is also how an Omni clip is extended, since POST /videos/extend is Veo-only, and the joins can be made invisible with one precaution at the start. Because every edit opens and closes on the same frames as its source, the only thing that shows at a join is the difference between the source’s own first and last frame. Remove it at the seed: generate a still of the presenter at rest with POST /images, make the first clip with that still as both startImage and endImage, and every edit chained from it opens and closes on that same frame. Join the run with POST /videos/concatenate and the cuts land still-to-still. The quiet beat that pins each clip to the still also shows at a join as a hold, about half a second before the presenter speaks and a third of a second of settling after, so trim it there: roughly trimEnd: 0.375 on every clip but the last and trimStart: 0.458 on every clip but the first leaves two frames of rest across each cut. Measured on a 4-second talking head, the joins were then indistinguishable from the motion inside the clips and the longest hold was an eighth of a second.
Google’s V2V moderation is non-deterministic. The exact same body can land on a content-safety reject (IDENTIFIABLE_PERSON_SAFETY, CHILD_SAFETY, etc.) on one attempt and a clean success on the next. A simple resubmit usually clears it — no input changes needed.
Inline @-mention markers (optional)
You can place @-markers inside prompt that resolve to the body parameters you supply. Markers are case-insensitive and opt-in — a prompt with no markers behaves exactly like before.
Supported markers (one per body slot):
| Marker | Resolves to |
|---|---|
@character_1..7 | matching character_N body param |
@referenceImage_1..7 | matching referenceImage_N body param |
@referenceAudio_1..5 | matching referenceAudio_N body param |
@referenceVideo_* | NOT supported inline — pass referenceVideo_1 as a body field. The API returns 400 if it appears in prompt. |
Rules:
- Each marker MUST have a matching body slot supplied. Otherwise the API returns 400 with
'character_N' was not provided in the request body. - A slot referenced inline still goes into the upstream reference array — markers are positional/grounding info, not a replacement for the body params.
- Same slot may be inlined multiple times in one prompt; the upstream reference array de-duplicates by id.
- Body params supplied but NOT inlined still flow to the upstream reference array (unchanged behavior).
- Unknown strings like
@gmail.comor@anyword_1(no_<digit>after a recognized slot name) pass through as literal prompt text.
Example:
{
"model": "omni-flash",
"prompt": "@character_1 sitting on @referenceImage_1, @character_2 standing beside, @referenceImage_2 visible in the background, smooth camera dolly",
"character_1": "user:123-email:...-character:...-imgs:1",
"character_2": "user:123-email:...-character:...-imgs:1",
"referenceImage_1": "user:123-email:...-image:...",
"referenceImage_2": "user:123-email:...-image:..."
}
Async & callbacks
asyncis optional, enables fire-and-forget mode (default:false). Whentrue, returns immediately with201 Createdand job metadata. Poll GET /jobs/jobIdfor completion status. Useful for avoiding long request timeouts since video generation takes 60-180 seconds.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. Useful for tracking jobs on your end.
Captcha
captchaToken,captchaRetry,captchaOrderare optional, mutually exclusive captcha parameters. See Captcha Parameters for details.
Responses
-
Videos generated successfully. All video data is in the
media[]array.{ "jobId": "j1731859234567v-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": "A serene mountain landscape at sunset with camera slowly panning right", "requestData": { "videoGenerationRequestData": { "videoModelControlInput": { "videoModelName": "veo_3_1_t2v", "videoGenerationMode": "VIDEO_GENERATION_MODE_TEXT_TO_VIDEO", "videoAspectRatio": "VIDEO_ASPECT_RATIO_LANDSCAPE" } }, "clientPlatform": "CLIENT_PLATFORM_WEB" }, "mediaStatus": { "mediaGenerationStatus": "MEDIA_GENERATION_STATUS_SUCCESSFUL" }, "visibility": "PRIVATE" }, "video": { "generatedVideo": { "seed": 123456, "prompt": "A serene mountain landscape at sunset with camera slowly panning right", "model": "veo_3_1_t2v", "baseImageMediaGenerationId": "", "isLooped": false, "aspectRatio": "VIDEO_ASPECT_RATIO_LANDSCAPE" }, "dimensions": { "length": "8s" }, "operation": { "name": "a1d95d21-75d2-482d-a354-14ef8802ce66" } }, "mediaGenerationId": "user:12345-email:6a6f...-video:a1d95d21-75d2-482d-a354-14ef8802ce66", "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 generation is processing in the background.Use GET /jobs/
jobIdto poll for completion status.{ "jobid": "j1731859234567v-u12345-email:jo***@gmail.com-bot:google-flow", "type": "video", "status": "created", "created": "2025-11-17T12:34:56.789Z", "request": { "async": true, "prompt": "A serene mountain landscape at sunset with camera slowly panning right", "email": "jo***@gmail.com", "model": "veo-3.1-fast", "aspectRatio": "landscape", "duration": 8, "count": 2, "seed": 123456, "replyUrl": "https://your-domain.com/webhook", "replyRef": "custom-reference-123" }, "response": { "operations": [ { "operation": { "name": "1450903d...redacted...9c86f0" }, "sceneId": "1450903d...redacted...9c86f0", "status": "MEDIA_GENERATION_STATUS_PENDING" }, { "operation": { "name": "f2eec9bd...redacted...e16f7a" }, "sceneId": "f2eec9bd...redacted...e16f7a", "status": "MEDIA_GENERATION_STATUS_PENDING" } ], "captcha": { "service": "AntiCaptcha", "taskId": "14af1dbb-885c-4e25-8121-7a79489dfd0e", "durationMs": 5357 } } } -
Invalid request (validation error, mode conflict, email mismatch, or content policy violation).
Validation error:
{ "error": "Model 'veo-3.1-quality' does not support R2V mode (MULTI_REFERENCE_NO_STYLE)" }Content policy error:
{ "jobId": "j1731859234567v-u12345-email:jo***@gmail.com-bot:google-flow", "error": "API error: 400", "code": 400, "response": { "error": { "code": 400, "message": "Request contains an invalid argument.", "status": "INVALID_ARGUMENT", "details": [ { "@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "PUBLIC_ERROR_UNSAFE_GENERATION" } ] } } }Content policy error (direct):
{ "error": { "code": 400, "message": "Request contains an invalid argument.", "status": "INVALID_ARGUMENT", "details": [ { "@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "PUBLIC_ERROR_UNSAFE_GENERATION" } ] } }All operations failed (during polling):
The top-level
erroris always the same sentence, so the reason is inresponse.failureReasons— see Why a generation failed below.{ "jobId": "j1731859234567v-u12345-email:jo***@gmail.com-bot:google-flow", "error": "All operations failed", "code": 400, "response": { "failureReasons": [ "PUBLIC_ERROR_MINOR" ], "operations": [ { "operation": { "name": "a789f1...redacted", "error": { "code": 3, "message": "PUBLIC_ERROR_MINOR" } }, "sceneId": "a629a9...redacted", "status": "MEDIA_GENERATION_STATUS_FAILED" } ] } } -
Invalid API token.
{ "error": "Unauthorized" } -
Subscription expired or insufficient credits.
{ "error": "Account has no subscription or subscription expired" } -
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 not configured.
{ "error": "Google Flow account [email protected] not found" } -
Video generation polling timeout (after 10 minutes).
{ "error": "Video generation polling timeout after 120 attempts (600s)" } -
Google returns several
RESOURCE_EXHAUSTED429 variants. Check thereasonfield and respond accordingly:Reason Meaning Scope What to do Cooldown PUBLIC_ERROR_USER_REQUESTS_THROTTLEDGoogle is throttling the account (the token was fine). Uploads and generation are metered separately All models on the account — or uploads only, when POST /assets triggered it Run fewer parallel requests, or switch accounts. From POST /assets, retrying the same one rarely clears it ~30 min. From POST /assets, treat it as a minimum — it can persist 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 } -
596 Session Error
Google session refresh failed. The account needs to be reconfigured. Delete the account using DELETE /accounts/
emailand add it again by strictly following the procedure in Setup Google Flow.{ "error": "Failed to refresh session: 500 Internal Server Error" }
Model
-
Video generation completed. Returns full video data with signed URLs.
{ jobId: string // Job identifier media: Array<{ // All video data lives here name: string // Media identifier (raw UUID) projectId: string workflowId?: string workflowStepId?: string // e.g. "CAE" mediaMetadata: { createTime?: string // ISO 8601 timestamp mediaTitle?: string // Original prompt requestData?: object // Full Google Flow request payload (model, mode, refs, etc.) mediaStatus: { mediaGenerationStatus: string // MEDIA_GENERATION_STATUS_SUCCESSFUL | FAILED error?: { code: number; message: string } } visibility?: string // "PRIVATE" } video?: { generatedVideo: { seed: number prompt?: string model: string // veo_3_1_t2v | veo_3_1_i2v | veo_3_1_i2v_fl | veo_3_1_r2v | omni_flash_* etc. baseImageMediaGenerationId?: string // Source media ID for I2V/R2V (empty string for T2V) 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 email?: string model?: string aspectRatio?: string count?: number seed?: number startImage?: string endImage?: string referenceImage_1?: string referenceImage_2?: string referenceImage_3?: string replyUrl?: string replyRef?: string } response: { operations: Array<{ operation: { name: string // Operation identifier } sceneId: string // Scene identifier status: 'MEDIA_GENERATION_STATUS_PENDING' }> captcha?: { // Present when captcha was used service: string // Provider that generated final token or "UserProvided" taskId?: string // Task ID from provider (absent for UserProvided) durationMs: number // Total captcha solving time attempts?: Array<{ // All captcha attempts (if retries occurred) service: string taskId?: string durationMs: number success: boolean }> } } } -
Error response structure (applies to both sync and async modes).
{ jobId?: string // Present for job-related errors error: string // Error summary message code?: number // HTTP status code response?: { // API response with error details failureReasons?: string[] // Why every operation failed — Google's terminal codes and // classifier labels, de-duplicated. Present only when all // operations failed AND Google gave a reason. See // "Why a generation failed" above operations?: Array<{ // Present when operations failed operation: { name: string error?: { code: number; message: string } } sceneId: string status: string }> error?: { // Present for direct API errors code: number message: string status: string details?: Array<{ '@type': string reason: string }> } } // The following siblings appear only on the load-balancer empty-set 429, // i.e. when error === "no_eligible_account" (see 429 response tab). message?: string // Customer-facing explanation retryAfter?: string // ISO-8601 timestamp of earliest retry skipReasons?: Array<{ // Per-account filter rationale email: string reason: string // Google 429 reason that triggered the quarantine model: string // Quarantined model, or "*" for account-wide }> }
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
-
curl -X POST \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "prompt": "A serene mountain landscape at sunset with camera slowly panning right", "model": "veo-3.1-fast", "aspectRatio": "landscape", "duration": 8, "count": 2, "seed": 123456 }' \ "https://api.useapi.net/v1/google-flow/videos" > response.json # Extract video URLs using jq cat response.json | jq -r '.media[0].videoUrl' cat response.json | jq -r '.media[1].videoUrl' # Download videos using curl curl "$(cat response.json | jq -r '.media[0].videoUrl')" --output video_1.mp4 curl "$(cat response.json | jq -r '.media[1].videoUrl')" --output video_2.mp4 -
const token = 'YOUR_API_TOKEN'; const apiUrl = 'https://api.useapi.net/v1/google-flow/videos'; const response = await fetch(apiUrl, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: 'A serene mountain landscape at sunset with camera slowly panning right', model: 'veo-3.1-fast', aspectRatio: 'landscape', duration: 8, count: 2, seed: 123456 }) }); const result = await response.json(); console.log('Generated videos:', result.media.length); // Download videos for (const [index, item] of result.media.entries()) { console.log(`Video ${index + 1} seed:`, item.video?.generatedVideo?.seed); console.log(`mediaGenerationId:`, item.mediaGenerationId); console.log(`Video URL:`, item.videoUrl); // Download video (Node.js) const videoResponse = await fetch(item.videoUrl); const videoBuffer = await videoResponse.arrayBuffer(); const fs = require('fs'); fs.writeFileSync(`generated_video_${index + 1}.mp4`, Buffer.from(videoBuffer)); } -
import requests token = 'YOUR_API_TOKEN' api_url = 'https://api.useapi.net/v1/google-flow/videos' headers = { 'Authorization': f'Bearer {token}', 'Content-Type': 'application/json' } data = { 'prompt': 'A serene mountain landscape at sunset with camera slowly panning right', 'model': 'veo-3.1-fast', 'aspectRatio': 'landscape', 'duration': 8, 'count': 2, 'seed': 123456 } response = requests.post(api_url, headers=headers, json=data) result = response.json() print(f"Generated {len(result['media'])} videos") # Download videos for index, item in enumerate(result['media']): video = item.get('video', {}).get('generatedVideo', {}) print(f"Video {index + 1} seed:", video.get('seed')) print(f"mediaGenerationId:", item.get('mediaGenerationId')) # Download video video_response = requests.get(item['videoUrl']) with open(f'generated_video_{index + 1}.mp4', 'wb') as f: f.write(video_response.content)