Build IMAGE_REF tags in Python for up to 10 Omni references
A Python helper turns a list of up to 10 image URLs into an Omni Flash prompt with matching IMAGE_REF tags, 0-based, in list order. Runs as written.

Gemini Omni Flash 1.1 on Sume refers to reference images by tag, <IMAGE_REF_0> for the first URL in reference_image_urls, <IMAGE_REF_1> for the second and so on up to <IMAGE_REF_9>. A short Python helper can build the tags from the list so the numbers never drift from the order.
The rule
The Video Router docs allow at most ten reference images and three reference videos, and number the tags from zero in list order. If you reorder the list, the tags must change with it, or the prompt will point at the wrong picture.
| List | Max items | First tag | Last tag |
|---|---|---|---|
| reference_image_urls | 10 | <IMAGE_REF_0> | <IMAGE_REF_9> |
| reference_video_urls | 3 | <VIDEO_REF_0> | <VIDEO_REF_2> |
The helper
The script prints a request body. It makes no network call, so you can run it before you spend anything. Send the body to POST /v1/video-router/generate.
import json
refs = {
"mug": "https://example.com/mug.png",
"desk": "https://example.com/desk.png",
"logo": "https://example.com/logo.png",
}
assert 1 <= len(refs) <= 10, "Omni takes 1 to 10 images"
tag = {name: f"<IMAGE_REF_{i}>" for i, name in enumerate(refs)}
prompt = (f"Place the {tag['mug']} on the {tag['desk']}, "
f"with the {tag['logo']} facing the camera.")
body = {"model": "gemini-omni-flash-1.1", "prompt": prompt,
"reference_image_urls": list(refs.values()),
"resolution": "720p", "duration": 5,
"aspect_ratio": "16:9", "mode": "async"}
print(json.dumps(body, indent=2))Cost of this request
Five seconds at 720p is 5 x 12.5 = 62.5, rounded up to 63 cents. Three references or ten make no difference to the billable amount. Use the dictionary keys as your own labels in logs, so a failed take can be traced to which asset was named.
Keep each image a clear, public HTTPS URL. A private link or an unsupported file type will fail the job.
The same length at every tier
For reference, a 5-second Omni Flash clip at each resolution. Every price is the seconds times the billable rate, rounded up to a whole cent, as of 2026-10-08.
| Resolution | Arithmetic | Billed |
|---|---|---|
| 360p | 5 x 3.75 = 18.75 cents | $0.19 |
| 720p | 5 x 12.5 = 62.5 cents | $0.63 |
| 1080p | 5 x 18.75 = 93.75 cents | $0.94 |
| 4K | 5 x 37.5 = 187.5 cents | $1.88 |
Limits to remember
These apply to every request on this page, from the Video Router and Video generation docs:
- Length is 3 to 10 whole seconds in generation modes; an edit takes no duration.
- Aspect ratio is 16:9 or 9:16; an edit takes no aspect ratio.
- Native synced audio is always on, and
generate_audio: falseis rejected. - There is no
bitrate_mode, no reference audio and noseed. - Billing is the provider list times 1.25 per output second, reserved at submit and shown in
usage.cost.
Sources
Related posts
More in Developers
- Bulk run item completed is not success: check before you publish
A Sume Format bulk child can be completed without being a success. Read each result and use per-child webhooks; no queue webhook exists.
- Bun 1.4.2 fixes an AsyncLocalStorage leak: per-request Sume keys
Bun 1.4.2 fixes an AsyncLocalStorage leak from 1.4.1. Carry a per-request Sume Idempotency-Key in the store, and check your Bun version first.
- bun test for a Sume webhook verifier: five cases to run on Bun 1.4.2
Five bun:test cases for a Sume webhook verifier: fresh, tampered, stale, rotation and an empty secret, plus a pin to Bun 1.4.2 or later. Run on Bun 1.4.0.
- Lost video callback? Sweep pending jobs after the 10-attempt window
Sume retries a job webhook 10 times, 30 seconds apart. If none gets through, the job is still done. A 30-line sweeper that polls pending jobs recovers it.
Written by Sume