Rendering and download
Queue the mp4 that turns a generated video into a file, poll its progress, and get a download URL that is known to resolve.
Rendering is the second of a video's two jobs. Generation produces the script, the voiceover, the transcript and the visuals; the render turns all of that into one mp4.
Rendering is asynchronous and can take longer than generation. POST /videos/{id}/render returns as soon as the job is queued, with
render.status: "QUEUED". Poll GET /videos/{id}/render, or subscribe to
video.render_completed.
Queue a render
curl -X POST https://app.shortfast.com/api/v1/videos/7bQxL2mF9dR4tK1sv/render \
-H "Authorization: Bearer sf_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Idempotency-Key: 8c3f1b90-77aa-4e51-b0d2-1e9f4a6c2d33"The call clears any previous renderUrl, because the file it pointed at is about
to be replaced. Passing process.render: true when the video is created (or on
POST /videos/{id}/process) chains the render automatically as soon as
generation finishes, which saves a poll-then-call round trip.
Poll the render
GET /videos/{id}/render is the cheap endpoint to poll: it returns the render
job and the collapsed lifecycle status, without the rest of the video.
curl https://app.shortfast.com/api/v1/videos/7bQxL2mF9dR4tK1sv/render \
-H "Authorization: Bearer sf_live_xxxxxxxxxxxxxxxxxxxxxxxx"{
"data": {
"videoId": "7bQxL2mF9dR4tK1sv",
"status": "rendering",
"render": {
"status": "STARTED",
"progress": 62,
"queuedAt": "2026-08-06T09:18:02.000Z",
"startedAt": "2026-08-06T09:18:05.000Z",
"error": null
},
"renderUrl": null
}
}render.status is QUEUED, STARTED, COMPLETED or FAILED, and
render.progress is a percentage from 0 to 100. status is the collapsed
lifecycle value (draft, processing, rendering, ready, failed), the same
one GET /videos filters on: poll on it if you only care whether the video is
usable yet.
Never queue a second render for a video that is already QUEUED or STARTED.
Use an Idempotency-Key so a retry after a client timeout replays the first
response rather than starting the work twice.
When a render is refused
A render charges credits in proportion to the video's duration and counts against the monthly render limit, so it can be refused before the job is queued:
| Code | Why | What clears it |
|---|---|---|
warning-no-credits | The balance will not cover this video's duration | Buy credits |
warning-plan-renders | The plan's monthly render limit is used up | Upgrade |
warning-plan-render-time | This video is longer than the plan's per-render ceiling | Upgrade |
The refusal arrives as a 400 with that code. The same vocabulary comes back
on render.error.code when a job fails after it started, alongside
error.blocked (an expected limit rather than a crash) and error.action (the
fix: upgrade, credits, connect, configure or none).
Read the live ceilings for the connected workspace from GET /workspace
(limits.maxRendersPerMonth, limits.maxRenderSeconds) or, with the counters
already used, from GET /stats. maxRendersPerMonth is null on plans that do
not cap renders, and it caps generations too. See
Credits and limits.
Download the file
curl https://app.shortfast.com/api/v1/videos/7bQxL2mF9dR4tK1sv/download \
-H "Authorization: Bearer sf_live_xxxxxxxxxxxxxxxxxxxxxxxx"{
"data": {
"videoId": "7bQxL2mF9dR4tK1sv",
"url": "https://cdn.shortfast.com/uploads/renders/7bQxL2mF9dR4tK1sv.mp4"
}
}Use this endpoint rather than caching media.renderUrl. For type: ai the
finished video is stitched from its per-scene clips on demand, and a stored URL
that no longer resolves is rebuilt rather than handed back dead. That work is why
the call can take a few seconds, and why it is the only URL guaranteed to
resolve.
A video with no render yet is refused with 400 warning-source-not-ready.
media.renderUrl on the video document is still useful as a signal (it is null
until a render completes, and cleared whenever the output goes stale), just not
as a durable link.
Anything that invalidates the stitched output clears renderUrl for you: adding,
editing or removing an AI scene, regenerating a scene's still, or
queuing a fresh render. Re-render before publishing after any of those.