'Use either first/end frame fields or reference_*_urls, not both'
A first frame pins the opening shot; references only guide it. Video Router refuses to mix them, so pick one mode per job and chain jobs if you need both.

Video Router answers "Use either first/end frame fields or reference_*_urls, not both." when a request carries image_url or first_frame_url together with any reference_image_urls, reference_video_urls or reference_audio_urls. Choose one mode per job: image-to-video with frames, or reference-to-video with references.
Frames and references are different jobs
A first frame fixes what the opening picture is. A reference list tells the model what a subject, a motion or a sound should look like without fixing any frame. Sume's docs describe the two as separate modes, and on the Video Router the validation refuses the mix. The /v1/videos route handles the same overlap differently: if both arrays are sent, frame_images controls the mode and the request runs as image-to-video. That route is covered in the related post on which field wins.
| You send | Result |
|---|---|
| image_url only | Image-to-video |
| image_url and end_image_url | Image-to-video with two anchors, if the model has an end frame |
| reference_image_urls only | Reference-to-video |
| image_url and reference_image_urls | 400: Use either first/end frame fields or reference_*_urls, not both. |
| end_image_url and reference_video_urls | Also a 400, plus the end-frame rule if there is no first frame |
Special cases worth knowing
Some catalog entries blur the line on purpose. For gemini-omni-flash-1.1 the catalog notes that one reference image with no first or end frame runs as reference-to-video. For grok-imagine-video-1.5 a single reference_image_urls entry stands in for the source frame, so there the one-image case is accepted; two reference images are refused with a message telling you to use image_url.
Chaining instead of mixing
If you need both an exact opening and a character reference, run two steps. First make the opening shot with the frame fields. Then, for the next shot, use a frame extracted from it as the new first frame, or switch to references for a shot that can start anywhere. The snippet decides the mode from what you hold and refuses the mix before sending. It only prints the body.
def build(prompt, model, first=None, last=None, refs=None):
if (first or last) and refs:
raise ValueError("pick frames or references, not both")
body = {"model": model, "prompt": prompt}
if first:
body["image_url"] = first
if last:
body["end_image_url"] = last
if refs:
body["reference_image_urls"] = list(refs)
return body
print(build("A courier hands over a parcel", "seedance-2.5",
refs=["https://example.com/courier.png"]))
When not to chase both
Many briefs ask for a precise first frame and consistent character looks at once. In practice the first frame already carries the character, so references add little in the opening shot. Use references for later shots that must keep the same person, and keep each job to one mode. A cheap short test per mode tells you faster than any argument which one your subject needs. Keep the test clip at the model's lowest resolution and shortest duration; the point is to learn which mode holds the subject, not to produce a final shot.
If a request fails on this rule in production, look first for a shared template that always adds a reference list, then a per-shot frame on top of it. Removing the template default usually fixes the whole batch.
Sources
Related posts
More in Developers
- /v1/videos 409 job_failed vs job_not_completed in retry loops
On /v1/videos/{id}/content, 409 job_not_completed is retryable and means keep polling; 409 job_failed is not retryable. Branch on the code, not the 409.
- /v1/videos canonical_slug: the stable model id to store
Store canonical_slug (or id) from GET /v1/videos/models, not the display name. On Sume ids are bare, like seedance-2, with no org prefix.
- /v1/videos status expired: in the enum, never sent by Sume
The /v1/videos status enum includes expired for OpenRouter compatibility, but Sume never emits it. Handle it as terminal anyway; five other statuses occur.
- /v1/videos id and generation_id are one Sume job id: store one
On Sume the id and generation_id in a /v1/videos poll are the same job id, unlike OpenRouter's two ids. Store one and reuse it on /v1/jobs routes.
Written by Sume