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.

4 min readSume
All posts

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.

Default waiting behavior of Sume media routes, read 2026-10-12
RouteDefault modeWaits for you?Where the result is read
POST /v1/video-framesasync, pinnedNever; mode sync is not honoredGET /v1/video-frames/:id
POST /v1/video-inspectsyncUp to 30 s, else 202GET /v1/video-inspect/:id
POST /v1/video-trimasyncOnly with mode sync, 30 sGET /v1/jobs/:id/result
POST /v1/video-filterasyncOnly with mode sync, 30 sGET /v1/jobs/:id/result
POST /v1/audio-detachasyncOnly with mode sync, 30 sGET /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_id from the 202 and poll that ID.
  • Treat resource_status: ready as the only signal that frames[] is populated.
  • Check each url for null before downloading.
  • Use format: png and max_edge when you need lossless stills at a known size.

Sources

Related posts

More in Media tools

All Media tools posts

Written by Sume