Animate a locally generated image with Sume video: first frame

Made a still with a local open-weights model? Host it at a public HTTPS URL, send it as first_frame to POST /v1/videos, and poll. Plus the licence check first.

5 min readSume
All posts

Yes: put the image at a public HTTPS URL, then send it to POST /v1/videos as a frame_images entry with frame_type set to first_frame, and poll the returned polling_url. Sume has no file upload API, so the URL step is yours.

The workflow suits anyone who generates stills on their own GPU with an open-weights model and wants motion without also running a video model locally. Check the still's licence before you animate it for client work.

What does Sume need from the local file?

Sume's media input rules accept public HTTPS URLs only. Localhost, private-network, signed or private URLs and non-HTTPS URLs are rejected, and there is no upload endpoint. A file sitting in your output folder is not reachable until you host it.

  • Host the PNG or JPG on any public HTTPS storage you control, with no signed query string.
  • Keep the URL alive until the job finishes, since the service fetches it during the job.
  • Use a still that already has the framing you want; the first frame is the start of the clip.
  • Do not use a tunnel to your laptop; private-network and localhost targets are refused.

Which video models take a first frame?

Do not hard-code a list. GET /v1/videos/models returns supported_frame_images per model, and frame_images takes precedence over input_references when both are sent. This request lists the ids whose row contains first_frame:

curl -s https://api.sume.com/v1/videos/models \
  -H "Authorization: Bearer $SUME_API_KEY" \
  | python3 -c "import json,sys; [print(m['id']) for m in json.load(sys.stdin)['data'] if 'first_frame' in (m.get('supported_frame_images') or [])]"

How do you submit the clip?

Pick an id from that list, then post the URL. Use resolution and aspect_ratio, not size, which returns 400, and do not send seed, which is not accepted. The response carries id and polling_url; poll until status is completed, then read unsigned_urls[0]. A failed status carries an error field.

import os, time, requests

H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
body = {
    "model": os.environ["VIDEO_MODEL"],
    "prompt": "Slow push-in, soft wind, no cuts",
    "frame_images": [{
        "type": "image_url",
        "image_url": {"url": os.environ["STILL_URL"]},
        "frame_type": "first_frame",
    }],
}
r = requests.post("https://api.sume.com/v1/videos", headers=H, json=body, timeout=60)
r.raise_for_status()
job = r.json()
while True:
    s = requests.get(job["polling_url"], headers=H, timeout=30).json()
    if s["status"] in ("completed", "failed"):
        break
    time.sleep(5)
print(s.get("unsigned_urls") or s.get("error"))

Is the still yours to animate commercially?

That depends on the model that made it, not on Sume. Ideogram's licensing page (read 2026-10-03) lists a free non-commercial tier and a separate commercial tier for outputs, so a still from the non-commercial weights is not cleared for a paid ad. Read the licence of whichever model made the image before you send it to a video model, and keep a copy with the project.

What to check before animating a local still, read 2026-10-03
CheckWhere to lookWhy it matters
Licence of the image modelThe model card or vendor licensing pageNon-commercial terms may bar paid use of outputs
Public URLOpen it in a private browser windowSume fetches it without your login
Model supports first_framesupported_frame_images in /v1/videos/modelsOther rows may not take a frame
Parametersresolution and aspect_ratiosize returns 400; seed is not accepted

Sources

Related posts

More in Use cases

All Use cases posts

Written by Sume