'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.

4 min readSume
All posts

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.

Alias handling on POST /v1/video-router/generate on main (read 2026-10-05)
You sendResult
duration: 12Accepted, 12 s
duration_seconds: 12Accepted, treated as duration 12
duration: 12 and duration_seconds: 12Accepted
duration: 8 and duration_seconds: 12400, 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 duration while the UI fills duration_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

All Developers posts

Written by Sume