Port an OpenRouter video client to Sume: base URL, key, model ids

Sume's /v1/videos follows the OpenRouter video wire. Three edits move a client: base URL, API key, bare model id. The six differences that still bite.

3 min readSume
All posts

A client written for the OpenRouter video API moves to Sume with three edits: the base URL becomes https://api.sume.com/v1/videos, the key becomes SUME_API_KEY, and model ids lose their org/ prefix. Video generation says the surface matches the OpenRouter video API field for field, so submit, polling_url and unsigned_urls keep their shapes.

The part that needs care is the short list of places where Sume differs on purpose. Read it before you flip production traffic.

The port in Python

Submit returns id, polling_url, status and model. Poll the URL until the status is terminal. Sume spells the canceled state cancelled on this wire, so test for it.

import os, time, requests

BASE = "https://api.sume.com/v1/videos"  # was https://openrouter.ai/api/v1/videos
H = {"Authorization": "Bearer " + os.environ["SUME_API_KEY"]}

r = requests.post(BASE, headers=H, timeout=30, json={
    "model": "wan-3.0",  # bare catalog id, no org/ prefix
    "prompt": "Slow push-in on a ceramic mug, steam rising",
    "duration": 5,
    "resolution": "720p",
    "aspect_ratio": "16:9",
})
r.raise_for_status()
job = r.json()
while job["status"] not in ("completed", "failed", "cancelled"):
    time.sleep(10)
    job = requests.get(job["polling_url"], headers=H, timeout=30).json()
print(job["status"], job.get("unsigned_urls"))

What differs from OpenRouter

These rows come from the Sume differences table in the docs.

Sume docs, read 2026-10-08
AreaOpenRouterSume
Model idsorg/slugBare ids such as seedance-2
sizeAccepted when the model lists sizes400 unsupported_parameter; use resolution + aspect_ratio
provider.optionsForwarded upstreamNon-empty value returns 400 unsupported_parameter
seedMany models accept itNo v1 model accepts it
Webhookvideo.generation.* eventsSume's job envelope, x-sume-webhook-signature
RetriesNo idempotency on the routeSend Idempotency-Key; a replay returns the original job

Cost of the check

A five-second Wan 3.0 clip at 720p is $0.625 on Sume, so a smoke test of the port costs well under a dollar. Run it with an Idempotency-Key and a second identical call should return the same job.

Fetch GET /v1/videos/models once and compare supported_durations with what your client sends. The catalog, not a cached list, decides what is valid.

A migration checklist

Treat the port as a short, testable change rather than a rewrite. The wire stays the same, so most of the risk sits in model ids and in the few parameters that Sume rejects on purpose.

  • Replace the base URL and key, then list GET /v1/videos/models and map every model id your client used to a bare Sume id.
  • Delete seed, size and any non-empty provider.options from request builders; each returns a 400 rather than being ignored.
  • Add an Idempotency-Key to the submit call. OpenRouter has none on this route, so a ported client probably never sent one.
  • If you used webhooks, change the receiver: the signature header and the event names are Sume's, not OpenRouter's.
  • Run one cheap job end to end and download the file from the content URL with the same Authorization header.

Why the order matters

Do the catalog check first. A model that exists under an org/slug name upstream may be a different bare id here, or may not be in the catalog at all, and a 404 at submit is cheaper to find in a test than in production. The /v1/videos/models response also tells you which resolutions, aspect ratios and durations each model accepts, so you can validate the request in your own code before it leaves.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume