'duration_seconds must match duration': send one field, not two
Video Router accepts duration or duration_seconds as the same value. Send both with different numbers and you get a 400. Send one, or send two equal ones.

Video Router treats duration and duration_seconds as two spellings of one number. If a request carries both and they differ, it is refused with "duration_seconds must match duration when both are provided." Send only one, or make sure they come from the same variable.
What the four cases do
The schema normalizes the alias after validation: the stored value is duration, taken from duration first and duration_seconds second. So a client that already speaks one of the two names needs no change; the failure shows up only in wrappers that fill both from different places, for example a default of 8 in one layer and a user value of 12 in another.
| You send | Result |
|---|---|
| duration: 12 | Accepted, 12 s |
| duration_seconds: 12 | Accepted, treated as duration 12 |
| duration: 12 and duration_seconds: 12 | Accepted |
| duration: 8 and duration_seconds: 12 | 400, message above, path duration_seconds |
Where the mismatch comes from
Typical sources are a form that keeps a hidden default, a retry helper that re-adds the original field after the user edited the other one, and SDK wrappers that expose both names for convenience.
- A config default that fills
durationwhile the UI fillsduration_seconds. - A JSON merge of two partial request objects, each with its own spelling.
- An agent that copies a field name from older docs and a newer one from the catalog.
Why the API refuses instead of picking one
Picking a winner silently would hide a bug in your own code. A clip billed for 12 seconds when your UI promised 8 is a more expensive surprise than a 400. The refusal is cheap: it happens in validation, before the reserve is taken, so you can retry freely. The same rule applies to the frame aliases (image_url and first_frame_url, end_image_url and last_frame_url), which must also agree when both are present.
Normalize in one place
The helper below picks one value and drops the alias before sending. It fails loudly on a real conflict instead of letting the API do it, and it never touches the API key until you send.
import os
import requests
def pick_duration(body: dict) -> dict:
a, b = body.pop("duration", None), body.pop("duration_seconds", None)
if a is not None and b is not None and a != b:
raise ValueError(f"duration {a} conflicts with duration_seconds {b}")
value = a if a is not None else b
return {**body, **({} if value is None else {"duration": value})}
body = pick_duration({
"model": "wan-3.0",
"prompt": "A paper boat crossing a puddle, overhead shot",
"resolution": "720p",
"duration_seconds": 12,
})
print(body)
if os.environ.get("SUME_API_KEY") and os.environ.get("SEND") == "1":
r = requests.post("https://api.sume.com/v1/video-router/generate",
headers={"Authorization": "Bearer " + os.environ["SUME_API_KEY"]},
json=body, timeout=60)
print(r.status_code)
Limits of the alias
The alias only exists on the Video Router. The OpenRouter-compatible POST /v1/videos route documents duration alone in its parameter table, so use that name when you move between the two. Neither spelling lets you go outside the model's range; see the ceiling table in the related duration post.
A submit sent with SEND=1 creates a real, billed job; leave it unset to just print the body.
Sources
Related posts
More in Developers
- Edit a finished Omni clip with video_url instead of re-rolling it
Send an existing clip as video_url to gemini-omni-flash-1.1 on Sume with an edit prompt. Resolution defaults to 720p; send no duration or aspect_ratio.
- Edit a photo with an Agent Completion: attach it, read output.images
Send the photo in attachments to POST /v1/agent/completions with a spend cap, poll the run, read the edit from output.images. Python code and limits.
- Per-request character limits: Eleven v4 10,000 vs Sume TTS 20,000
Eleven v4 takes up to 10,000 characters per generation, Sume TTS 1 to 20,000. A 45,000-character script is 5 requests versus 3, with a chunking script.
- ElevenLabs Music can sign MP3s with C2PA; what to log for a Sume track
The ElevenLabs Music API has sign_with_c2pa for MP3 output only. Sume's Music docs name no audio credential, so keep a record of job, prompt and routed model.
Written by Sume