Video frames API returns 202, not 200: poll GET /v1/video-frames/:id
Sume video frames always answers 202 and ignores mode sync. Poll GET /v1/video-frames/:id until resource_status is ready, then read frames[].url.

POST /v1/video-frames always returns 202. It does not return 200 with the stills, and sending mode: "sync" does not change that, because the route pins itself to async. The request_id in the response is the frames job ID, so poll GET /v1/video-frames/:id until resource_status is ready, then read frames[{t,url,width,height}].
This catches people who copy a pattern from a sync endpoint and look for frames in the first response. There are none yet; there is only the queued job.
What does the first response contain?
A 202 with request_id (equal to the video_frames_id) and a video_frames object. The stills arrive later as durable media.sume.com image artifacts. The source clip must already be a media.sume.com artifact or asset in your workspace, and you send exactly one of at[] (1 to 24 times in seconds) or fps (above 0, up to 2).
curl -X POST https://api.sume.com/v1/video-frames \
-H "Authorization: Bearer $SUME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: frames-check-007" \
-d '{"video_url": "https://media.sume.com/artifacts/artf_demo/talk.mp4", "at": [0, 2.5]}'
curl https://api.sume.com/v1/video-frames/$REQUEST_ID \
-H "Authorization: Bearer $SUME_API_KEY"How is this different from the other media endpoints?
Each Sume media route has its own waiting rule, and mixing them up is the usual cause of an empty first response. Frames is the strictest: it never waits. Trim, filter and detach default to async and let you opt in to a 30 second wait. Inspect defaults the other way.
| Route | Default mode | Waits for you? | Where the result is read |
|---|---|---|---|
| POST /v1/video-frames | async, pinned | Never; mode sync is not honored | GET /v1/video-frames/:id |
| POST /v1/video-inspect | sync | Up to 30 s, else 202 | GET /v1/video-inspect/:id |
| POST /v1/video-trim | async | Only with mode sync, 30 s | GET /v1/jobs/:id/result |
| POST /v1/video-filter | async | Only with mode sync, 30 s | GET /v1/jobs/:id/result |
| POST /v1/audio-detach | async | Only with mode sync, 30 s | GET /v1/jobs/:id/result |
What can go wrong while polling?
A single frame can fail without failing the job: that entry comes back with url: null. An at value outside 0 <= t < duration fails the job with frame_time_out_of_range and tells you the probed duration, and a source over 300 seconds fails with duration_out_of_range. Sources over 90 seconds can add the warning low_confidence_long_video, which does not stop the job.
Frames are billed by their own Modal compute, with the cost held at submit and never exceeded, and the job does not use an admission seat. If a poll loop is interrupted, resubmitting with the same Idempotency-Key returns the same job instead of queueing a second extract.
- Read
request_idfrom the 202 and poll that ID. - Treat
resource_status: readyas the only signal thatframes[]is populated. - Check each
urlfor null before downloading. - Use
format: pngandmax_edgewhen you need lossless stills at a known size.
Sources
Related posts
More in Media tools
- Video trim 400 video_trim_range_required: add end or duration
The Sume video trim API returns video_trim_range_required when the body has start but neither end nor duration. Send exactly one of the two; here is the fix.
- Which Sume media routes need a flag, and which are on prod?
Remix media, reference ingest and the trending-videos rebuild are flag-gated; video inspect, frames, trim, detach, filter and timeline are on dest and prod.
- Whistle word timestamps to burned captions with Sume's words field
Turn Whistle's on-device word timings into burned-in captions: map them to words with text, start and end, then call Sume video captions with no STT run.
- Open and close a clip with a white flash: the fade filter
Fade a clip in from white and out to white with two fade filters in Sume video filter. Out time comes from the clip duration; audio is untouched.
Written by Sume