Get Asset Download URL
June 15, 2026 (August 11, 2026)
Table of contents
- What this endpoint accepts
- Request Headers
- Path Parameters
- Query Parameters
- Responses
- Model
- Examples
- 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:
- POST /assets — an image or video you uploaded
- POST /images — a generated image
- POST /videos — a generated video
- POST /videos/upscale — an upscaled video
- POST /videos/extend — an extended video
- GET /jobs/
jobId— any of the above, read back from a stored job
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}
API tokenis required, see Setup useapi.net for details.
Path Parameters
mediaGenerationIdis required, URL-encoded. Anyimageorvideoreference 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
rawis optional. Accepted values aretrueand1. Any other value is rejected with400. 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
-
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> -
The path parameter could not be parsed as a valid
mediaGenerationId, or it is a character/voice reference type (onlyimageandvideoare accepted on this endpoint — use GET /characters/refor GET /voices/reffor those).{ "error": "Invalid mediaGenerationId format: not-a-valid-ref-id" }rawwas given a value other thantrueor1.{ "error": "Parameter raw must be 'true' or '1', got 'yes'" }?raw=truewas used with an image. Request it withoutraw, or readencodedImagefrom the original generation response.{ "error": "mediaGenerationId type must be 'video', got 'image'" } -
Invalid API token.
{ "error": "Unauthorized" } -
The
mediaGenerationIdbelongs to a different useapi.net user than the one identified by theAuthorizationtoken. EachmediaGenerationIdencodes its owning user id; requests across users are rejected before any Google call is made.{ "error": "Unauthorized access to user:99999 detected in mediaGenerationId" } -
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
rawwill not help and neither will retrying later — check themediaGenerationIditself.{ "error": "Media a1d95d21-...redacted... not found" } -
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..." } -
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-Afterheader 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=trueis 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=truereads 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/
emailand 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')