Retrieve Job Status
November 17, 2025 (July 27, 2026)
Table of contents
Retrieve the status and details of a specific image or video generation job by its job ID.
This endpoint is particularly useful when using async mode for video generation, or when tracking job progress via webhooks using the replyUrl parameter.
To get load balancing statistics across all jobs, use GET /jobs.
https://api.useapi.net/v1/google-flow/jobs/
jobId
Request Headers
Authorization: Bearer {API token}
API tokenis required, see Setup useapi.net for details.
Path Parameters
jobIdis required, the unique job identifier returned from POST /images or POST /videos.
Responses
-
Returns the job record with current status and details.
Video job (completed):
{ "jobid": "j1731859234567v-u12345-email:jo***@gmail.com-bot:google-flow", "type": "video", "status": "completed", "created": "2025-11-17T12:34:56.789Z", "updated": "2025-11-17T12:37:23.456Z", "request": { "email": "jo***@gmail.com", "prompt": "A serene mountain landscape at sunset with camera slowly panning right", "model": "veo-3.1-fast", "aspectRatio": "landscape", "count": 2, "seed": 123456, "async": true, "replyUrl": "https://your-domain.com/webhook" }, "response": { "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": { "...": "..." }, "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-...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": 14845, "captcha": { "service": "CapSolver", "taskId": "abc123...", "durationMs": 3500, "attempts": [ { "service": "CapSolver", "taskId": "abc123...", "durationMs": 3500, "success": true } ] } } }Image job (completed):
{ "jobid": "j1731859345678i-u12345-email:an***@gmail.com-bot:google-flow", "type": "image", "status": "completed", "created": "2025-11-17T12:45:12.345Z", "updated": "2025-11-17T12:45:34.678Z", "request": { "email": "an***@gmail.com", "prompt": "A serene mountain landscape at sunset with vibrant colors", "model": "nano-banana-2-lite", "aspectRatio": "landscape", "count": 4, "replyUrl": "https://your-domain.com/webhook", "replyRef": "custom-reference-123" }, "response": { "media": [ { "name": "…redacted…", "image": { "generatedImage": { "seed": 987654, "mediaGenerationId": "user:12345…redacted…", "fifeUrl": "https://flow-content.google/image/…redacted…?Expires=…&KeyName=labs-flow-prod-cdn-key&Signature=…", "prompt": "A serene mountain landscape at sunset with vibrant colors" } } } ], "captcha": { "service": "AntiCaptcha", "taskId": "abc123...", "durationMs": 3200, "attempts": [ { "service": "AntiCaptcha", "taskId": "abc123...", "durationMs": 3200, "success": true } ] } } }Job failed:
{ "jobid": "j1731859567890i-u12345-email:an***@gmail.com-bot:google-flow", "type": "image", "status": "failed", "created": "2025-11-17T13:00:12.345Z", "updated": "2025-11-17T13:00:45.678Z", "request": { "email": "an***@gmail.com", "prompt": "Generate an image", "model": "nano-banana-2" }, "error": "API error: 500", "code": 500, "response": { "error": { "code": 500, "message": "Internal error encountered.", "status": "INTERNAL" } } } -
Invalid API token.
{ "error": "Unauthorized" } -
Job belongs to a different user.
{ "error": "Access denied - job belongs to different user" } -
Job not found in database.
{ "error": "Job not found" } -
Invalid job ID format.
{ "error": "Invalid job ID format" } -
Job has expired (jobs are retained for 7 days).
{ "error": "Job has expired" }
Model
-
Video generation job structure.
{ jobid: string // Unique job identifier type: 'video' // Job type status: 'created' | 'started' | 'completed' | 'failed' created: string // ISO 8601 creation timestamp updated?: string // ISO 8601 last update timestamp request: { email?: string prompt: string model?: 'veo-3.1-quality' | 'veo-3.1-fast' | 'veo-3.1-lite' | 'veo-3.1-lite-low-priority' aspectRatio?: 'landscape' | 'portrait' | 'square' count?: number // 1-4 seed?: number startImage?: string // mediaGenerationId for I2V mode endImage?: string // mediaGenerationId for I2V-FL mode referenceImage_1?: string // mediaGenerationId for R2V mode referenceImage_2?: string // mediaGenerationId for R2V mode referenceImage_3?: string // mediaGenerationId for R2V mode async?: boolean // Fire-and-forget mode replyUrl?: string // Webhook URL for callbacks replyRef?: string // Custom reference for callbacks } response?: { media: Array<{ 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 (for debugging/research) 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 }> } } error?: string // Error message (if failed) errorDetails?: string // Additional error details code?: number // HTTP status code (if failed) } -
Image generation job structure.
{ jobid: string // Unique job identifier type: 'image' // Job type status: 'created' | 'started' | 'completed' | 'failed' created: string // ISO 8601 creation timestamp updated?: string // ISO 8601 last update timestamp request: { email?: string prompt: string model?: 'nano-banana-2-lite' | 'nano-banana-2' | 'nano-banana-pro' // older jobs may carry 'imagen-4' | 'nano-banana' aspectRatio?: 'landscape' | 'portrait' count?: number // 1-4 seed?: number reference_1?: string // mediaGenerationId reference_2?: string // mediaGenerationId reference_3?: string // mediaGenerationId replyUrl?: string // Webhook URL for callbacks replyRef?: string // Custom reference for callbacks } response?: { media: Array<{ name: string image: { generatedImage: { seed: number mediaGenerationId: string fifeUrl?: string // Signed image URL (valid ~6h). Absent if the URL could not be produced encodedImage?: string // Base64 image bytes. Present only when fifeUrl is absent prompt: string } } }> captcha?: { // Captcha metadata (for debugging/research) 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 }> } } error?: string // Error message (if failed) errorDetails?: string // Additional error details code?: number // HTTP status code (if failed) }
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
-
# Get job status curl "https://api.useapi.net/v1/google-flow/jobs/j1731859234567v-u12345-email:jo***@gmail.com-bot:google-flow" \ -H "Authorization: Bearer {API token}" # Poll for completion (check every 5 seconds) while true; do STATUS=$(curl -s "https://api.useapi.net/v1/google-flow/jobs/j1731859234567v-u12345-email:jo***@gmail.com-bot:google-flow" \ -H "Authorization: Bearer {API token}" | jq -r '.status') echo "Status: $STATUS" if [[ "$STATUS" == "completed" || "$STATUS" == "failed" ]]; then break fi sleep 5 done -
const apiToken = 'your-api-token' const jobId = 'j1731859234567v-u12345-email:jo***@gmail.com-bot:google-flow' // Get job status async function getJobStatus(jobId) { const response = await fetch(`https://api.useapi.net/v1/google-flow/jobs/${jobId}`, { headers: { 'Authorization': `Bearer ${apiToken}` } }) if (!response.ok) { throw new Error(`HTTP ${response.status}: ${await response.text()}`) } return await response.json() } // Poll until completion async function waitForCompletion(jobId, intervalMs = 5000) { while (true) { const job = await getJobStatus(jobId) console.log(`Status: ${job.status}`) if (job.status === 'completed') { console.log('Job completed!', job.response) return job } if (job.status === 'failed') { console.error('Job failed:', job.error) throw new Error(job.error) } await new Promise(resolve => setTimeout(resolve, intervalMs)) } } // Usage const job = await waitForCompletion(jobId) -
import requests import time api_token = 'your-api-token' job_id = 'j1731859234567v-u12345-email:jo***@gmail.com-bot:google-flow' # Get job status def get_job_status(job_id: str) -> dict: response = requests.get( f'https://api.useapi.net/v1/google-flow/jobs/{job_id}', headers={'Authorization': f'Bearer {api_token}'} ) response.raise_for_status() return response.json() # Poll until completion def wait_for_completion(job_id: str, interval_sec: int = 5) -> dict: while True: job = get_job_status(job_id) print(f"Status: {job['status']}") if job['status'] == 'completed': print("Job completed!", job['response']) return job if job['status'] == 'failed': raise Exception(f"Job failed: {job.get('error')}") time.sleep(interval_sec) # Usage job = wait_for_completion(job_id) # Access video URLs (for video jobs) if job['type'] == 'video': for item in job['response']['media']: if 'videoUrl' in item: print(f"Video URL: {item['videoUrl']}") # Access image URLs (for image jobs) if job['type'] == 'image': for media in job['response']['media']: if 'image' in media and 'generatedImage' in media['image']: print(f"Image URL: {media['image']['generatedImage']['fifeUrl']}")