How preview works
Preview reuses the exact same render pipeline and renderer asPOST /v1/renders —
same engine, same JSON schema — just restricted to a slice of the timeline and
downscaled. What you see in a preview is exactly what the full render produces at
that timestamp.
- Submit
POST /v1/previewwith arender_requestcomposition. - Choose what you want:
dry_run: true— validate only, no render, returns instantly.at: <seconds>— a single frame.window: { from, to }— a short clip, with audio.contact_sheet: { ... }— a grid of frames sampled across the timeline.
- Get a response immediately —
200for a dry run, no job (a contact-sheet dry run also includes the sampling plan, still with no job). Any non-dry-run preview — frame, window, or contact sheet — returns202with a job queued. - Poll like any render —
job.id(prefixrender_) works withGET /v1/renders/{id}. Preview jobs are free: excluded from your usage totals and fromGET /v1/renders.
Free validation (dry_run)
Set dry_run: true to run Framelane’s linter without spending a render: duplicate
element IDs, dangling or self-referencing transitions, an empty timeline, an
element or caption that starts after the composition ends, an entrance motion timed
later than the element it belongs to, an audio element hidden with visible: false
(which drops the track rather than muting it), a track matte whose source layer is
never on screen while the element it drives is, the canvas-space findings
(off-frame boxes, colliding text, unreadable sizes and contrast, a drop shadow whose
offset rounds to nothing), the structure of the v5 blocks (operations, frames, components
and instances, below), and, if you pass target_duration_sec, a timeline that misses your
target length.
A dry run also runs the full translator and reports every feature the real submit
would reject — an unsupported field, a motion preset used on the wrong element type,
a zero-offset shadow, a word_animation combined with motion[], a transition
pointing at a non-video, a dangling group member or matte source. Each arrives as
severity: "error" with the same message the 422 would carry, so ok: true means
the composition will submit. Run it before every POST /v1/renders.
ok is false only when a violation is severity: "error" — a missed
target_duration_sec, for example, is a "warning" and never blocks you.
Each violation says where it happened twice: path, an index path such as
elements[0].children[2].text_color, and pointer, the same place as an RFC 6901 JSON
pointer (/elements/0/children/2/text_color). target_id is the id of the element, frame,
instance or group the finding is about (inside an instance, its expanded path, such as
card1/title), or null when it names none.
Structural findings (v5 blocks)
Frames, instances,operations and components are checked on the whole document before
the renderer sees them, and each code mirrors the renderer’s own name. All are errors but
the two marked warning. The canvas-space findings (off-frame, overlap, size, contrast,
shadow) measure root elements only: inside a frame or an instance they say nothing.
A layout key on a frame or an instance (
layout, gap, padding, align and the like)
is a 422 of type layout_key_unknown that names the key and the frame: the renderer lays
nothing out, so place each node at its own x and y in design px.
Dry-run ok vs. submit-path ok
The same two fields appear in two different roles, and only one of them is a gate:
The dry run is strictly the more informative of the two, because it runs the full
translator: a feature the submit path rejects shows up there as an
error, whereas on
the submit path that same problem is a 422 and there is no body to attach it to. What
the advisory lint adds is the handful of findings a strict translation does not
cover — a self-referencing transition, a fully off-canvas element, a sub-pixel drop
shadow — reported on the render you just paid for so you are not left comparing the
artifact against your intent.
If the linter itself fails, the submit still goes through and you get a single
LINT_UNAVAILABLE warning with ok: true. That is a server-side fault, not a
statement about your composition.
Frame and window previews
Passat for a single frame, or window: { from, to } for a short clip (audio
trimmed and muxed for free). Both render at a small draft width by default (480
px; raise width up to 3840 for a sharper look) and dispatch a real, cheap render
job.
job.id via GET /v1/renders/{id} exactly like a full render.
at and window are mutually exclusive. A window.to past the end of the
composition is clamped down to the composition’s duration rather than rejected —
you may get a shorter clip than requested, never an error. A window.from at or
past the composition’s duration is rejected, with 422 invalid_request. An at
past the end of the composition is clamped to the last frame, not rejected.Contact sheets: see the whole edit in one image
Setcontact_sheet to get back a single grid PNG sampling frames across the
timeline — enough to check an entire edit in one glance (or one vision call),
without downloading and scrubbing a video.
{ "detail": "balanced" } on a 10-second
composition budgets ~10 cells; max_cells caps that, shown here at 4 to keep the
example short). The sheet field describes the plan immediately (even on a
dry_run, before any render happens):
Combine with
window to sample only part of the timeline instead of the whole
thing. Once the render completes, job.output.url is a single PNG —
columns × cellWidth pixels wide, rows × cellHeight tall — with each cell’s
frame and burned-in label ("0:04 element_start") matching sheet.cells[i] in
order. GET /v1/renders/{id}/poster redirects to a signed URL for the sheet PNG —
the embeddable counterpart to /download.
Request reference
Endpoints
Good to know
- Preview jobs cost nothing and are excluded from
GET /v1/rendersandGET /v1/workspace/usage— build as many as you need while iterating. - A preview is pixel-identical to the equivalent full render at the same timestamp, modulo resolution — the renderer takes no different code path for a preview, so what you approve (once scaled up) is what you’ll get.
- Preview requests never accept
Idempotency-Key— every call creates a new job. - If an element references media you haven’t finished uploading yet, a real
preview (
dry_run: false) returns422 asset_not_ready; adry_run: truevalidation tolerates it and lints the rest of the composition anyway.

