Preview a HyperFrames composition as PNG stills with the Sume API

POST /v1/hyperframes-previews returns 1-6 PNG stills of a composition without rendering an MP4. Request shape, polling, and what the frames are not.

4 min readSume
All posts

To look at a HyperFrames composition before you pay for a render, call POST https://api.sume.com/v1/hyperframes-previews with the composition and an optional at list of 1 to 6 times. Sume captures PNG stills, no MP4 is produced and no encode runs, and you read the pictures from GET /v1/hyperframes-previews/{id} once the job completes.

Everything below comes from the live Sume OpenAPI document (read 2026-10-10) and the docs pages on jobs and results and errors and rate limits. The route has no standalone docs page yet, so the schema is the source of truth.

What does the request body need?

The body is strict: unknown keys are rejected. The only required key is hyperframes_composition, and it describes the same document a render would use, so a preview cannot show something the final bake would not.

Optional keys are at (the times to capture), check (covered in a separate post), and the usual delivery keys mode, webhook_url and wait_timeout_seconds.

Composition fields on POST /v1/hyperframes-previews (OpenAPI, read 2026-10-10)
FieldRuleNotes
htmlstring, at least 32 charactersThe composition markup.
clips1 to 200 itemsEach has id (max 64), source_url (a URI) and relative_path matching assets/ or fonts/ plus a plain file name.
width, heightintegers 16 to 4096Pixels.
duration_secondsabove 0, at most 60Seconds.
at1 to 6 numbers, each 0 or moreOmit it and Sume samples 10%, 50% and 97% of the clamped duration.
source_tool, html_sha256, fit_duration_to_mediaoptionalMetadata and a duration option.

How do I submit and read the stills?

The submit call answers 202 with the usual job envelope: request_id, hyperframes_preview_id, status_url, result_url, events_url, cancel_url, next_poll_after_seconds and terminal. Store the id, then poll. The resource read returns status, resource_status, frames and error.

Each entry in frames carries t, url, width and height. The PNGs are durable media.sume.com artifacts stamped with the stage preview. That stamp matters: they are pictures of a composition that has not been rendered, not a deliverable.

The Python sample uses only the standard library. It sends the key as x-api-key, which is the single credential header the API accepts per request.

import json, os, time, urllib.request

def call(method, path, body=None):
    req = urllib.request.Request(
        "https://api.sume.com" + path, method=method,
        data=json.dumps(body).encode() if body else None,
        headers={"x-api-key": os.environ["SUME_API_KEY"], "User-Agent": "sume-example/1.0",
                 "Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=30) as r:
        return json.load(r)["data"]

comp = {"html": "<div id='root' data-composition-id='main'></div>",
        "clips": [{"id": "clip1", "source_url": os.environ["CLIP_URL"],
                   "relative_path": "assets/clip1.mp4"}],
        "width": 1080, "height": 1920, "duration_seconds": 12}
sub = call("POST", "/v1/hyperframes-previews",
           {"hyperframes_composition": comp, "at": [0.5, 6, 11]})
pid = sub["hyperframes_preview_id"]
while True:
    prev = call("GET", f"/v1/hyperframes-previews/{pid}")
    if prev["status"] in ("completed", "failed", "canceled"):
        break
    time.sleep(3)
for f in prev["frames"] or []:
    print(f["t"], f["url"])

Which defaults and limits should I plan around?

at is capped at six, so a preview is a spot check, not a contact sheet of the whole timeline. When you omit it, the last sample sits at 97% of the clamped duration and is held inside the timeline rather than on the frame after it.

The published OpenAPI lists no Idempotency-Key parameter on this route, unlike most generation routes. Treat a lost response as a reason to look at your own stored id, not as a reason to submit again, and use the job list to find the earlier job if you must.

Delivery works like other jobs: async is the default, sync and subscribe wait at most 30 seconds and then hand you the job to poll, and webhook sends terminal events only.

  • A failed preview is a normal terminal state: read error on the resource.
  • The preview is metered compute rather than a fixed-price model call, so read the amount from your usage ledger rather than assuming a number.

How does this compare with the other preview routes?

Sume has several read-only media routes that look similar from a distance. Video frames pulls stills out of a video that already exists. Reference ingest analyses a reference clip. The HyperFrames preview is the one that works on a composition you wrote, before any video exists.

That is why the request carries the clips and fonts list: Sume stages exactly what a bake would stage, boots Chromium once, and screenshots the requested times. Because it boots once for all requested times, one call with several at values is the natural shape, not a loop of single-time calls.

A practical loop for a templated ad pipeline is to preview at the first frame, the midpoint and the last second of the cut, compare the three stills against a brand checklist, and render only when they pass. The preview is the cheaper place to find a bad crop, because nothing is encoded.

When is a still the wrong tool?

Stills tell you about layout, type and cropping at a few moments. They do not prove motion timing, audio sync or encode quality, because nothing is encoded. If you need those, render.

If your question is about collisions with a caption band or contrast, use the check mode instead of reading pixels by eye; it returns a structured report.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume