Upload Assets

November 17, 2025 (September 19, 2026)

Table of contents

  1. Request Headers
  2. Path Parameters
  3. Request Body
  4. Responses
  5. Model
    1. Image upload response
    2. Video upload response
  6. Examples
  7. Try It

Upload assets to Google Flow. Supports image and video uploads:

Content-Type File Extension Max size
image/png png 20 MB
image/jpeg jpeg 20 MB
image/webp webp 20 MB
video/mp4 mp4 100 MB

POST raw content using Make.com and similar nocode tools.

Uploads stay on the Google account after the generation that used them finishes, and nothing removes them on its own. Over a long-running integration they add up. To see what an account is holding, use GET /assets/media/email — it marks uploads with likelyUpload — and clear them with DELETE /assets/email.

https://api.useapi.net/v1/google-flow/assets/email

Request Headers

Authorization: Bearer {API token}
Content-Type: select from the table above
  • API token is required, see Setup useapi.net for details.
  • Content-Type is required, see table above.

Path Parameters

  • email is optional. The email address of the Google Flow account to use (URL-encoded if necessary).

    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 image generation job statistics to select the healthiest account.

    Note: The endpoint can be called as either POST /assets (no email) or POST /assets/:email (explicit email).

Request Body

Binary file contents (raw bytes). Set Content-Type to match the file:

  • image/png, image/jpeg, image/webp for image uploads
  • video/mp4 for video uploads (used as referenceVideo_1 in omni-flash V2V edit)

The image and video paths return slightly different response shapes — see the 200 response below.

Responses

  • 200 OK

    Upload successful. The response shape differs between image and video uploads — see both below.

    Image upload response

    Returned when Content-Type is image/png, image/jpeg, or image/webp.

    {
      "media": {
        "name": "ff9aa5cc-...redacted...",
        "projectId": "5c37e988-...redacted...",
        "workflowId": "69603881-...redacted...",
        "workflowStepId": "CAE",
        "mediaMetadata": {
          "createTime": "2026-02-27T16:34:25.128454Z",
          "requestData": {
            "clientPlatform": "CLIENT_PLATFORM_WEB"
          },
          "visibility": "PRIVATE"
        },
        "image": {
          "userUploadedImage": {
            "aspectRatio": "IMAGE_ASPECT_RATIO_UNSPECIFIED"
          },
          "dimensions": {
            "width": 1024,
            "height": 1024
          }
        }
      },
      "workflow": {
        "name": "69603881-...redacted...",
        "metadata": {
          "displayName": "upload.webp",
          "createTime": "2026-02-27T16:34:25.128454Z",
          "primaryMediaId": "ff9aa5cc-...redacted..."
        },
        "projectId": "5c37e988-...redacted..."
      },
      "mediaGenerationId": {
        "mediaGenerationId": "user:12345-email:6a6f...-image:ff9aa5cc-...redacted..."
      },
      "width": 1024,
      "height": 1024,
      "email": "jo***@gmail.com"
    }
    
    Field Description
    media Full media object from Google Flow API with upload details and dimensions
    media.image.dimensions Image dimensions as reported by Google (width and height)
    workflow Workflow metadata including display name and project ID
    mediaGenerationId.mediaGenerationId Reference ID for use in subsequent API calls. Format: user:{userid}-email:{hex_encoded_email}-image:{internal_media_id}. Pass this to GET /assets/mediaGenerationId to read back the original bytes via a signed URL.
    width Image width in pixels
    height Image height in pixels
    email Account email that was used for the upload (useful when email was omitted and auto-selected via load balancing)

    Video upload response

    Returned when Content-Type is video/mp4. Use the returned mediaGenerationId as referenceVideo_1 on POST /videos with model: "omni-flash" to drive V2V edit.

    {
      "media": {
        "name": "07c542b0-...redacted...",
        "projectId": "5c37e988-...redacted...",
        "workflowId": "290a1f7a-...redacted...",
        "workflowStepId": "CAE",
        "mediaMetadata": {
          "createTime": "2026-05-20T23:04:23.089821Z",
          "requestData": { "clientPlatform": "CLIENT_PLATFORM_WEB" },
          "mediaStatus": { "mediaGenerationStatus": "MEDIA_GENERATION_STATUS_PENDING" },
          "visibility": "PRIVATE"
        },
        "video": {
          "dimensions": { "width": 1280, "height": 720, "length": "11.940s" },
          "videoOffset": { "startOffset": "0s", "endOffset": "11.940s" },
          "userUploadedVideo": { "encodedVideo": "" }
        }
      },
      "mediaGenerationId": {
        "mediaGenerationId": "user:12345-email:6a6f...-video:07c542b0-...redacted..."
      },
      "durationSeconds": 11.94,
      "width": 1280,
      "height": 720,
      "email": "jo***@gmail.com"
    }
    
    Field Description
    media Full media object from Google Flow API with upload details and dimensions (mirrors the image response shape, with a video{} sub-object instead of image{})
    media.video.dimensions Video dimensions as reported by Google (width, height, length)
    mediaGenerationId.mediaGenerationId Reference ID for use as referenceVideo_1 on POST /videos. Format: user:{userid}-email:{hex_encoded_email}-video:{internal_media_id}. Also accepted by GET /assets/mediaGenerationId to read back the original bytes via a signed URL.
    durationSeconds Video duration in seconds. Use this to pre-compute endFrameIndex_1 for V2V edit: min(round(durationSeconds × 24), 240)
    width Video width in pixels
    height Video height in pixels
    email Account email that was used for the upload
  • 400 Bad Request

    Invalid request (empty content, unsupported content type, file too large, or content policy violation).

    General error:

    {
      "error": "File size (25165824) is over 20971520 bytes"
    }
    

    Content policy error:

    {
      "error": {
        "code": 400,
        "message": "Request contains an invalid argument.",
        "status": "INVALID_ARGUMENT",
        "details": [
          {
            "@type": "type.googleapis.com/google.rpc.ErrorInfo",
            "reason": "PUBLIC_ERROR_TRUMP_FACE_DETECTED"
          }
        ]
      }
    }
    
  • 401 Unauthorized

    Invalid API token.

    {
      "error": "Unauthorized"
    }
    
  • 404 Not Found

    Account not found or not configured.

    {
      "error": "Google Flow account john@gmail.com not found"
    }
    
  • 429 Too Many Requests

    A 429 means Google is rate-limiting the account that handled your upload. Most image-upload 429s carry no reason code. The body is {"error":{"code":429,"message":"flow.google.com maseQ failed","status":"RESOURCE_EXHAUSTED","details":[]}}, with details empty and nothing to read a reason from — the message inside that error object names the upload call that failed, and is not the top-level message described below. That form is handled as USER_REQUESTS_THROTTLED all the same, so everything below applies to it unchanged: the same cooldown, the same Retry-After, the same upload-only quarantine, and the same top-level message and retryAfter fields added alongside Google’s error object.

    Some accounts instead carry one of two reasons:

    Reason Meaning Cooldown
    PUBLIC_ERROR_
    USER_QUOTA_REACHED
    The account’s overall Flow quota cap ~30 min
    PUBLIC_ERROR_
    USER_REQUESTS_THROTTLED
    Google is throttling uploads for this account ~30 min minimum, can persist

    Google meters uploads separately from generation, so USER_REQUESTS_THROTTLED on this endpoint does not mean the account is out of action. The same account can keep serving POST /images and POST /videos normally while every upload is refused.

    They also survive separately. An account that has stopped accepting uploads keeps every image already uploaded to it, and those mediaGenerationId values stay usable as reference_N on that account indefinitely. An account in that state is still worth generating on — it simply cannot take new source material. The limit also appears to clear after a period we are still measuring, so such an account is worth resting rather than replacing. Counting post-assets per account in GET /v2/account/stats shows how much of its upload allowance has been spent.

    The response carries a Retry-After header. Treat it as a minimum wait rather than an estimate of when the limit clears — it is a fixed cooldown, not a per-account prediction, and an upload throttle can sit on an account for hours or days. On USER_REQUESTS_THROTTLED the body also carries a message field spelling this out, and normally a retryAfter timestamp mirroring the header.

    If you passed email, that account is the one being limited — upload to a different account rather than retrying the same one. If you omitted email and have more than one account, the load balancer automatically routes around limited accounts, so you only see a 429 (error: "no_eligible_account") once every account is limited — see GET /jobs › Load Balancing Algorithm. An upload throttle only removes the account from upload routing, so generation requests still reach it.

    A video upload is the exception to everything above. It fails earlier, in Google’s upload-init step, and returns a plain {"error": "Video upload init failed: HTTP 429"} with no reason code, no Retry-After and no message, and it does not quarantine the account.

    On that no_eligible_account response, each skipReasons[].model reports what the account was quarantined for. Uploads carry no model, so entries armed by this endpoint report upload — a routing scope, not a model you can request. On this endpoint the only other value is * (account-wide). On the generation endpoints it is a real model name or *.

  • 503 Service Unavailable

    Google’s upload service is temporarily unavailable. The error is forwarded with a Retry-After header — wait a few seconds and retry. This endpoint uses no captcha provider, so the Captcha service failed 503 does not apply here.

    {
      "error": {
        "code": 503,
        "message": "Service temporarily unavailable.",
        "status": "UNAVAILABLE"
      }
    }
    
  • 596 Session Error

    Google session refresh failed. The account needs to be reconfigured. Delete the account using DELETE /accounts/email and add it again by strictly following the procedure in Setup Google Flow.

    {
      "error": "Failed to refresh session: 500 Internal Server Error"
    }
    

Model

Image upload response

{
  media: {                              // Full media object from Google Flow API
    name: string                        // Media UUID
    projectId: string
    workflowId: string
    workflowStepId: string
    mediaMetadata?: {
      createTime?: string               // ISO 8601 timestamp
      requestData?: object
      visibility?: string               // "PRIVATE"
    }
    image?: {
      userUploadedImage?: {
        aspectRatio: string             // "IMAGE_ASPECT_RATIO_UNSPECIFIED" | "LANDSCAPE" | "PORTRAIT"
      }
      dimensions?: {
        width: number
        height: number
      }
    }
  }
  workflow?: {                          // Workflow metadata
    name: string                        // Workflow UUID
    metadata?: {
      displayName?: string              // Original filename
      createTime?: string
      primaryMediaId?: string           // Same as media.name
    }
    projectId?: string
  }
  mediaGenerationId: {
    mediaGenerationId: string           // Reference ID: user:{userid}-email:{hex_encoded_email}-image:{internal_media_id}
  }
  width: number                         // Image width in pixels
  height: number                        // Image height in pixels
  email: string                         // Account email used for upload
  error?: string | {                    // Error: string (useapi.net) or object (Google API)
    code: number
    message: string
    status: string
    details: Array<{
      '@type': string
      reason: string
    }>
  }
}

Video upload response

Returned when Content-Type: video/mp4.

{
  media?: {                             // Mirrors image response shape (with video{} instead of image{})
    name: string
    projectId: string
    workflowId: string
    workflowStepId: string
    mediaMetadata: {
      createTime: string
      requestData: { clientPlatform: string }
      mediaStatus: { mediaGenerationStatus: string }
      visibility: string
    }
    video: {
      dimensions: { width: number; height: number; length: string }   // length is "11.940s"
      videoOffset: { startOffset: string; endOffset: string }
      userUploadedVideo: { encodedVideo: string }
    }
  }
  mediaGenerationId: {
    mediaGenerationId: string           // Reference ID: user:{userid}-email:{hex_encoded_email}-video:{internal_media_id}
                                        //   — use as referenceVideo_1 on POST /videos with model='omni-flash'
  }
  durationSeconds?: number              // Video duration in seconds
  width?: number                        // Video width in pixels (from upload finalize response)
  height?: number                       // Video height in pixels
  email: string                         // Account email used for upload
  error?: string | {                    // Error: string (useapi.net) or object (Google API)
    code: number
    message: string
    status: string
    details: Array<{
      '@type': string
      reason: string
    }>
  }
}

Examples

  • # Option 1: Without email (auto-selection/load balancing)
    curl -X POST \
         -H "Authorization: Bearer YOUR_API_TOKEN" \
         -H "Content-Type: image/jpeg" \
         --data-binary @/path/to/your/image.jpeg \
         "https://api.useapi.net/v1/google-flow/assets"
    
    # Option 2: With specific email
    curl -X POST \
         -H "Authorization: Bearer YOUR_API_TOKEN" \
         -H "Content-Type: image/jpeg" \
         --data-binary @/path/to/your/image.jpeg \
         "https://api.useapi.net/v1/google-flow/assets/john%40gmail.com"
    
  • const token = 'YOUR_API_TOKEN';
    
    // Option 1: Without email (auto-selection/load balancing)
    const apiUrl = 'https://api.useapi.net/v1/google-flow/assets';
    
    // Option 2: With specific email
    // const email = 'john@gmail.com';
    // const apiUrl = `https://api.useapi.net/v1/google-flow/assets/${encodeURIComponent(email)}`;
    
    // Load image - Example 1: Fetch from URL
    const imageUrl = "https://upload.wikimedia.org/wikipedia/commons/7/7d/Mona_Lisa_color_restoration.jpg";
    const responseImage = await fetch(imageUrl);
    const blob = await responseImage.blob();
    
    // Load image - Example 2: From local file (Node.js)
    // const fsp = require('fs').promises;
    // const imageFileName = "./cat.png";
    // const blob = new Blob([await fsp.readFile(imageFileName)]);
    
    // Load image - Example 3: From file input element
    // // <input id="image-file" type="file">
    // const imageFile = document.getElementById('image-file');
    // const blob = imageFile.files[0];
    
    const response = await fetch(apiUrl, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': blob.type || 'image/jpeg'
      },
      body: blob
    });
    
    const result = await response.json();
    console.log('Upload result:', result);
    console.log('Reference ID:', result.mediaGenerationId.mediaGenerationId);
    console.log('Account used:', result.email);
    
  • import requests
    from urllib.parse import quote
    
    token = 'YOUR_API_TOKEN'
    
    # Option 1: Without email (auto-selection/load balancing)
    api_url = 'https://api.useapi.net/v1/google-flow/assets'
    
    # Option 2: With specific email
    # email = 'john@gmail.com'
    # api_url = f'https://api.useapi.net/v1/google-flow/assets/{quote(email)}'
    
    # Load image - Example 1: Fetch from URL
    # image_url = "https://upload.wikimedia.org/wikipedia/commons/7/7d/Mona_Lisa_color_restoration.jpg"
    # response_image = requests.get(image_url)
    # file_content = response_image.content
    
    # Load image - Example 2: From local file
    # image_file_path = "./image.jpg"
    # with open(image_file_path, 'rb') as image_file:
    #     file_content = image_file.read()
    
    headers = {
        'Authorization': f'Bearer {token}',
        'Content-Type': 'image/jpeg'
    }
    
    response = requests.post(
        api_url,
        headers=headers,
        data=file_content
    )
    
    result = response.json()
    print('Upload result:', result)
    print('Reference ID:', result.get('mediaGenerationId', {}).get('mediaGenerationId'))
    print('Account used:', result.get('email'))
    

Try It