Get Asset Download URL

June 15, 2026 (August 11, 2026)

Table of contents

  1. What this endpoint accepts
  2. Request Headers
  3. Path Parameters
  4. Query Parameters
  5. Responses
  6. Model
  7. Examples
  8. Try It

Resolve a signed download URL for an image or video, or download the video bytes through us with ?raw=true.

Pass any mediaGenerationId of type image or video. It does not matter how you obtained it — an uploaded asset and a generated one behave identically here. The endpoints that hand you one are:

In each case it is the mediaGenerationId string itself, URL-encoded into the path. On POST /assets that is the nested mediaGenerationId.mediaGenerationId value, and on generation responses it sits on each entry of media[].

The endpoint has two response shapes, selected by the raw query parameter.

Request Response Content-Type
GET /assets/{mediaGenerationId} JSON carrying a signed URL you fetch yourself application/json
GET /assets/{mediaGenerationId}?raw=true the media bytes, streamed through useapi.net video/mp4

Use the default JSON form. The signed URL points straight at Google’s CDN and stays valid for ~6 hours, so you download the file directly from Google.

Reach for ?raw=true only when the JSON form cannot produce a URL. That is uncommon — Google issues those signed URLs from an endpoint that limits how often it will answer a given network address, and once in a while it withholds them for a few minutes to a few hours before clearing on its own. The raw form reads the media over a different route that the limit does not touch, which makes it a dependable fallback — at the cost of moving the whole file through us on every call. It is video only.

What this endpoint accepts

A video works with both forms. Ask without raw for a signed URL, or with ?raw=true for the bytes.

An image works with the signed URL only. Adding ?raw=true to an image returns 400. You do not need it for images: whenever an image URL is missing, POST /images puts the image itself in the response instead, base64-encoded in encodedImage.

A character or voice reference is not served here at all and returns 400 either way. Those have their own endpoints — GET /characters/ref and GET /voices/ref.

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

Request Headers

Authorization: Bearer {API token}

Path Parameters

  • mediaGenerationId is required, URL-encoded. Any image or video reference is accepted — see what this endpoint accepts above for the full list of endpoints that return one, and where the value sits in each response.

Query Parameters

  • raw is optional. Accepted values are true and 1. Any other value is rejected with 400. Omit it to receive JSON with a signed URL, which is the recommended form.

When raw is set, the response body is the media file itself rather than JSON:

Content-Type: video/mp4
Content-Disposition: attachment; filename="<mediaId>.mp4"

The bytes are streamed as they arrive, so the response carries no Content-Length and cannot be resumed part-way. Save the body to a file or pipe it onward. A video generated at 8 seconds and 720p is roughly 6-7 MB.

Responses

  • 200 OK

    Without raw, the body is JSON carrying the signed URL.

    {
      "url": "https://flow-content.google/image/ff9aa5cc-...?Expires=1785115591&KeyName=labs-flow-prod-cdn-key&Signature=...",
      "mediaGenerationId": "user:12345-email:6a6f...-image:ff9aa5cc-...redacted..."
    }
    

    With ?raw=true, the body is the video file itself and there is no JSON at all.

    Content-Type: video/mp4
    Content-Disposition: attachment; filename="a1d95d21-...redacted....mp4"
    
    <binary MP4 data>
    
  • 400 Bad Request

    The path parameter could not be parsed as a valid mediaGenerationId, or it is a character/voice reference type (only image and video are accepted on this endpoint — use GET /characters/ref or GET /voices/ref for those).

    {
      "error": "Invalid mediaGenerationId format: not-a-valid-ref-id"
    }
    

    raw was given a value other than true or 1.

    {
      "error": "Parameter raw must be 'true' or '1', got 'yes'"
    }
    

    ?raw=true was used with an image. Request it without raw, or read encodedImage from the original generation response.

    {
      "error": "mediaGenerationId type must be 'video', got 'image'"
    }
    
  • 401 Unauthorized

    Invalid API token.

    {
      "error": "Unauthorized"
    }
    
  • 403 Forbidden

    The mediaGenerationId belongs to a different useapi.net user than the one identified by the Authorization token. Each mediaGenerationId encodes its owning user id; requests across users are rejected before any Google call is made.

    {
      "error": "Unauthorized access to user:99999 detected in mediaGenerationId"
    }
    
  • 404 Not Found

    The owning Google Flow account (decoded from the mediaGenerationId) is not configured for this token.

    {
      "error": "Google Flow account [email protected] not found"
    }
    

    Google holds no media under this id. Both forms of this endpoint return the same thing here, so retrying without raw will not help and neither will retrying later — check the mediaGenerationId itself.

    {
      "error": "Media a1d95d21-...redacted... not found"
    }
    
  • 502 Bad Gateway

    Google answered in a way we do not recognize — neither a usable download link nor one of the known conditions above. This one is worth reporting to [email protected], because unlike the 404 and 503 cases it is not something you can resolve from your side.

    {
      "error": "Failed to resolve URL for media ff9aa5cc-...redacted..."
    }
    
  • 503 Service Unavailable

    The media exists but is not downloadable yet. Both of the cases below are temporary and clear on their own, and both carry a Retry-After header telling you how long to wait — read it rather than hard-coding a delay.

    Google is still processing the media. A freshly uploaded or just-generated asset spends a few seconds in this state, and ?raw=true is affected for the same window.

    Retry-After: 10
    
    {
      "error": "Media a1d95d21-...redacted... is not ready yet, retry in 10s"
    }
    

    Google is rate-limiting the requests we make to fetch signed URLs, which it does by network address rather than by account. This affects the default form only — ?raw=true reads the media over a different Google route and keeps working. See Missing media URLs for the full picture.

    Retry-After: 60
    
    {
      "error": "Media a1d95d21-...redacted... download URL is temporarily unavailable, retry in 60s"
    }
    
  • 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

Without raw:

{
  url: string                 // Signed CDN download URL, valid for ~6 hours
  mediaGenerationId: string   // Echo of the request path parameter
  error?: string              // Present only on error responses
}

With ?raw=true there is no model — a successful response is the raw MP4 body. Errors still return the JSON error shape above.

Examples

  • # Step 1: read back an asset uploaded earlier
    MEDIA_GEN_ID='user:12345-email:6a6f...-image:ff9aa5cc-...'
    
    ENCODED=$(printf %s "$MEDIA_GEN_ID" | jq -sRr @uri)
    
    RESP=$(curl -s \
      -H "Authorization: Bearer YOUR_API_TOKEN" \
      "https://api.useapi.net/v1/google-flow/assets/$ENCODED")
    
    # Step 2: fetch the signed URL directly to read the bytes
    URL=$(echo "$RESP" | jq -r .url)
    curl -L -o restored.png "$URL"
    
    # Fallback when no URL came back: stream the bytes through useapi.net.
    # ?raw=true accepts video mediaGenerationIds only.
    # -f makes curl fail on an error status instead of writing the JSON error to the file.
    VIDEO_GEN_ID='user:12345-email:6a6f...-video:a1d95d21-...'
    
    curl -f -s \
      -H "Authorization: Bearer YOUR_API_TOKEN" \
      -o restored.mp4 \
      "https://api.useapi.net/v1/google-flow/assets/$(printf %s "$VIDEO_GEN_ID" | jq -sRr @uri)?raw=true"
    
  • const token = 'YOUR_API_TOKEN';
    const mediaGenerationId = 'user:12345-email:6a6f...-video:a1d95d21-...';
    
    const apiUrl = `https://api.useapi.net/v1/google-flow/assets/${encodeURIComponent(mediaGenerationId)}`;
    
    const resp = await fetch(apiUrl, {
      headers: { 'Authorization': `Bearer ${token}` }
    });
    const { url } = await resp.json();
    
    let bytes;
    if (url) {
      // Preferred: download straight from Google
      const dl = await fetch(url);
      bytes = new Uint8Array(await dl.arrayBuffer());
    } else {
      // No URL available right now — stream the video through useapi.net instead (video only)
      const dl = await fetch(`${apiUrl}?raw=true`, {
        headers: { 'Authorization': `Bearer ${token}` }
      });
      if (!dl.ok) throw new Error(`raw download failed: ${dl.status}`);
      bytes = new Uint8Array(await dl.arrayBuffer());
    }
    console.log(`Downloaded ${bytes.length} bytes`);
    
  • import requests
    from urllib.parse import quote
    
    token = 'YOUR_API_TOKEN'
    media_generation_id = 'user:12345-email:6a6f...-video:a1d95d21-...'
    
    api_url = f'https://api.useapi.net/v1/google-flow/assets/{quote(media_generation_id, safe="")}'
    resp = requests.get(api_url, headers={'Authorization': f'Bearer {token}'})
    url = resp.json().get('url')
    
    if url:
        # Preferred: fetch Google's CDN directly
        dl = requests.get(url)
        data = dl.content
    else:
        # No URL available right now — stream the video through useapi.net instead (video only)
        dl = requests.get(api_url, params={'raw': 'true'},
                          headers={'Authorization': f'Bearer {token}'}, stream=True)
        dl.raise_for_status()
        data = dl.content
    
    with open('restored.mp4', 'wb') as f:
        f.write(data)
    print(f'Downloaded {len(data)} bytes')
    

Try It