Upload Assets

November 17, 2025 (September 1, 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 [email protected] not found"
    }
    
  • 429 Too Many Requests

    A 429 means Google is rate-limiting the account that handled your upload. For an image upload it carries 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.

    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 = '[email protected]';
    // 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 = '[email protected]'
    # 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